diff --git a/atrip.org b/atrip.org index a69fa03..c4840bf 100644 --- a/atrip.org +++ b/atrip.org @@ -8,7 +8,9 @@ The algorithm uses two main data types, the =Slice= and the ** The slice +The following section introduces the idea of a slice. +*** Prolog :noexport: #+begin_src c++ :tangle (atrip-slice-h) #pragma once #include @@ -26,6 +28,7 @@ struct Slice { using F = double; #+end_src +*** Introduction A slice is the concept of a subset of values of a given tensor. As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds of objects: @@ -35,13 +38,63 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds - the object \( \mathsf{T}(a,b)_{ij} \) which for every pair of \( a, b \) corresponds the \( N_\mathrm{o}^2 \)-sized tensor \( T^{ab}_{ij} \). +*** Location + +Every slice set, for instance, +\( S_k = \left\{ + a \mapsto \mathsf{T}(a)^{b}_{ij} + \mid + a \in A_k +\right\} \) +where \( A_k \) is some subset of +\( \mathsf{N}_\mathrm{v} \), +gets stored in some rank \( k \). +In general however, the number of elements in \( A_k \) can be bigger +than the number of processes \( n_p \). Therefore in order to uniquely +indentify a given slice in \( S_k \) we need two identifiers, +the rank \( k \), which tells us in which core's memory the slice is +allocated, and an additional tag which we will call =source=. + +The datatype that simply models this state of affairs +is therefore a simple structure: + +#+begin_src c++ :tangle (atrip-slice-h) + struct Location { size_t rank; size_t source; }; +#+end_src + +*** Type + +Due to the permutation operators in the equations +it is noticeable that for every one dimensional +slice and triple \( (a,b,c) \) +\begin{equation*} +a \mapsto \mathsf{t}(a) +\end{equation*} +one needs at the same time +\( \mathsf{t}(a) \), +\( \mathsf{t}(b) \) and +\( \mathsf{t}(c) \). +For two dimensional slices, i.e., slices of the form +\begin{equation*} +(a,b) \mapsto \mathsf{t}(a,b) +\end{equation*} +one needs in the equations the slices +\( \mathsf{t}(a,b) \), +\( \mathsf{t}(b,c) \) and +\( \mathsf{t}(a,c) \). +In addition, in the case of diagrams where +the integral \( V^{ab}_{ci} \) appears, +we additionaly need the permuted slices +from before, i.e. +\( \mathsf{t}(b,a) \), +\( \mathsf{t}(c,b) \) and +\( \mathsf{t}(c,a) \). + +This means, every slice has associated with it +a type which denotes which permutation it is. #+begin_src c++ :tangle (atrip-slice-h) - // ASSOCIATED TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - struct Location { size_t rank; size_t source; }; - enum Type { A = 10 , B @@ -57,53 +110,102 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds // The non-typed slice , Blank = 404 }; +#+end_src +*** State + +Every slice can be in different states and every state +denotes which function the slice is going to provide +and which relations they have between themselves. + +- Fetch :: + A slice is in state =Fetch= when it + has a valid data pointer that **must** be written to. + A =Fetch= slice should not live very long, this means + that after the database send and receive phase, + =Fetch= slices should be changed into =Dispatched= + in order to start the process of writing to the + data pointer from some other rank. +- Dispatched :: + A =Dispatched= slice indicates that at some point + send and receive MPI calls have been dispatched + in order to get the data. + However, the calls have just been dispatched and there + is no warranty for the data to be there, for that, + the slice must be unwrapped. +- Ready :: + =Ready= means that the data pointer can be read from + directly. +- SelfSufficient :: + A slice is =SelfSufficient= when its contents are located + in the same rank that it lives, so that it does not have to + fetch from no other rank. + This is important in order to handle the data pointers correctly + and in order to save calls to MPI receive and send functions. +- Recycled :: + =Recycled= means that this slice gets its data pointer from another + slice, so it should not be written to +- Acceptor :: + =Acceptor= means that the slice can accept a new slice, it is + the counterpart of the =Blank= type, but for states + +Again the implementation is a simple enum type. + +#+begin_src c++ :tangle (atrip-slice-h) enum State { - // Fetch represents the state where a slice is to be fetched - // and has a valid data pointer that can be written to Fetch = 0, - // Dispatches represents the state that an MPI call has been - // dispatched in order to get the data, but the data has not been - // yet unwrapped, the data might be there or we might have to wait. Dispatched = 2, - // Ready means that the data pointer can be read from Ready = 1, - // Self sufficient is a slice when its contents are located - // in the same rank that it lives, so that it does not have to - // fetch from no one else. SelfSufficient = 911, - // Recycled means that this slice gets its data pointer from another - // slice, so it should not be written to Recycled = 123, - // Acceptor means that the Slice can accept a new Slice, it is - // the counterpart of the Blank type, but for states Acceptor = 405 }; +#+end_src - struct Info { - // which part of a,b,c the slice holds - PartialTuple tuple; - // The type of slice for the user to retrieve the correct one - Type type; - // What is the state of the slice - State state; - // Where the slice is to be retrieved - // NOTE: this can actually be computed from tuple - Location from; - // If the data are actually to be found in this other slice - Type recycling; +*** The Info structure - Info() : tuple{0,0} - , type{Blank} - , state{Acceptor} - , from{0,0} - , recycling{Blank} - {} - }; +Every slice has an information structure associated with it +that keeps track of the **variable** type, state and so on. - using Ty_x_Tu = std::pair< Type, PartialTuple >; +#+begin_src c++ :tangle (atrip-slice-h) +struct Info { + // which part of a,b,c the slice holds + PartialTuple tuple; + // The type of slice for the user to retrieve the correct one + Type type; + // What is the state of the slice + State state; + // Where the slice is to be retrieved + Location from; + // If the data are actually to be found in this other slice + Type recycling; - // Names of the integrals that are considered in CCSD(T) + Info() : tuple{0,0} + , type{Blank} + , state{Acceptor} + , from{0,0} + , recycling{Blank} + {} +}; + +using Ty_x_Tu = std::pair< Type, PartialTuple >; +#+end_src + +*** Name + +CCSD(T) needs in this algorithm 5 types of tensor slices, +namely +\( V^{ij}_{ka} \), \( V^{ab}_{ci} \), +\( V^{ab}_{ij} \) +and two times \( T^{ab}_{ij} \). +The reason why we need two times the doubles +amplitudes is because in the doubles contribution +to the energy, the \( T \) amplidutes will be sliced +through one parameter for the particle contribution +and through two parameters for the hole contribution. + + +#+begin_src c++ :tangle (atrip-slice-h) enum Name { TA = 100 , VIJKA = 101 @@ -111,257 +213,352 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds , TABIJ = 201 , VABIJ = 202 }; +#+end_src - // DATABASE ==========================================================={{{1 +*** Database + +The database is a simple representation of the slices of a slice union. +Every element of the database is given by the name of the tensor it +represents and the internal information structure. + +#+begin_src c++ :tangle (atrip-slice-h) struct LocalDatabaseElement { Slice::Name name; Slice::Info info; }; +#+end_src + +A local database (of a given rank) and the global database is thus simply +a vector of these elements. + +#+begin_src c++ :tangle (atrip-slice-h) using LocalDatabase = std::vector; using Database = LocalDatabase; +#+end_src +*** MPI Types +#+begin_src c++ :tangle (atrip-slice-h) +struct mpi { - // STATIC METHODS =========================================================== - // - // They are useful to organize the structure of slices - - struct mpi { - - static MPI_Datatype vector(size_t n, MPI_Datatype const& DT) { - MPI_Datatype dt; - MPI_Type_vector(n, 1, 1, DT, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype sliceLocation () { - constexpr int n = 2; - // create a sliceLocation to measure in the current architecture - // the packing of the struct - Slice::Location measure; - MPI_Datatype dt; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] = {usizeDt(), usizeDt()}; - - // measure the displacements in the struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(&measure.rank, &displacements[j++]); - MPI_Get_address(&measure.source, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype enumDt() { return MPI_INT; } - static MPI_Datatype usizeDt() { return MPI_UINT64_T; } - - static MPI_Datatype sliceInfo () { - constexpr int n = 5; - MPI_Datatype dt; - Slice::Info measure; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] - = { vector(2, usizeDt()) - , enumDt() - , enumDt() - , sliceLocation() - , enumDt() - }; - - // create the displacements from the info measurement struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(measure.tuple.data(), &displacements[j++]); - MPI_Get_address(&measure.type, &displacements[j++]); - MPI_Get_address(&measure.state, &displacements[j++]); - MPI_Get_address(&measure.from, &displacements[j++]); - MPI_Get_address(&measure.recycling, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype localDatabaseElement () { - constexpr int n = 2; - MPI_Datatype dt; - LocalDatabaseElement measure; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] - = { enumDt() - , sliceInfo() - }; - - // measure the displacements in the struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(&measure.name, &displacements[j++]); - MPI_Get_address(&measure.info, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - }; - - static - PartialTuple subtupleBySlice(ABCTuple abc, Type sliceType) { - switch (sliceType) { - case AB: return {abc[0], abc[1]}; - case BC: return {abc[1], abc[2]}; - case AC: return {abc[0], abc[2]}; - case CB: return {abc[2], abc[1]}; - case BA: return {abc[1], abc[0]}; - case CA: return {abc[2], abc[0]}; - case A: return {abc[0], 0}; - case B: return {abc[1], 0}; - case C: return {abc[2], 0}; - default: throw "Switch statement not exhaustive!"; - } + static MPI_Datatype vector(size_t n, MPI_Datatype const& DT) { + MPI_Datatype dt; + MPI_Type_vector(n, 1, 1, DT, &dt); + MPI_Type_commit(&dt); + return dt; } + static MPI_Datatype sliceLocation () { + constexpr int n = 2; + // create a sliceLocation to measure in the current architecture + // the packing of the struct + Slice::Location measure; + MPI_Datatype dt; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] = {usizeDt(), usizeDt()}; - /** - ,* It is important here to return a reference to a Slice - ,* not to accidentally copy the associated buffer of the slice. - ,*/ - static Slice& findOneByType(std::vector &slices, Slice::Type type) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&type](Slice const& s) { - return type == s.info.type; - }); - WITH_CRAZY_DEBUG - WITH_RANK - << "\t__ looking for " << type << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error("Slice by type not found!"); - return *sliceIt; - } + // measure the displacements in the struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(&measure.rank, &displacements[j++]); + MPI_Get_address(&measure.source, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - /* - ,* Check if an info has - ,* - ,*/ - static std::vector hasRecycledReferencingToIt - ( std::vector &slices - , Info const& info - ) { - std::vector result; + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - for (auto& s: slices) - if ( s.info.recycling == info.type - && s.info.tuple == info.tuple - && s.info.state == Recycled - ) result.push_back(&s); + static MPI_Datatype enumDt() { return MPI_INT; } + static MPI_Datatype usizeDt() { return MPI_UINT64_T; } - return result; - } + static MPI_Datatype sliceInfo () { + constexpr int n = 5; + MPI_Datatype dt; + Slice::Info measure; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] + = { vector(2, usizeDt()) + , enumDt() + , enumDt() + , sliceLocation() + , enumDt() + }; - static Slice& - findRecycledSource (std::vector &slices, Slice::Info info) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&info](Slice const& s) { - return info.recycling == s.info.type - && info.tuple == s.info.tuple - && State::Recycled != s.info.state - ; - }); + // create the displacements from the info measurement struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(measure.tuple.data(), &displacements[j++]); + MPI_Get_address(&measure.type, &displacements[j++]); + MPI_Get_address(&measure.state, &displacements[j++]); + MPI_Get_address(&measure.from, &displacements[j++]); + MPI_Get_address(&measure.recycling, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find: recycling source of " - << pretty_print(info) << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice not found: " - + pretty_print(info) - + " rank: " - + pretty_print(Atrip::rank) - ); - WITH_RANK << "__slice__:find: " << pretty_print(sliceIt->info) << "\n"; - return *sliceIt; - } + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - static Slice& findByTypeAbc - ( std::vector &slices - , Slice::Type type - , ABCTuple const& abc - ) { - const auto tuple = Slice::subtupleBySlice(abc, type); - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&type, &tuple](Slice const& s) { - return type == s.info.type - && tuple == s.info.tuple - ; - }); - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find:" << type << " and tuple " - << pretty_print(tuple) - << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice not found: " - + pretty_print(tuple) - + ", " - + pretty_print(type) - + " rank: " - + pretty_print(Atrip::rank) - ); - return *sliceIt; - } + static MPI_Datatype localDatabaseElement () { + constexpr int n = 2; + MPI_Datatype dt; + LocalDatabaseElement measure; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] + = { enumDt() + , sliceInfo() + }; - static Slice& findByInfo(std::vector &slices, - Slice::Info const& info) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&info](Slice const& s) { - // TODO: maybe implement comparison in Info struct - return info.type == s.info.type - && info.state == s.info.state - && info.tuple == s.info.tuple - && info.from.rank == s.info.from.rank - && info.from.source == s.info.from.source - ; - }); - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find:looking for " << pretty_print(info) << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice by info not found: " - + pretty_print(info)); - return *sliceIt; - } + // measure the displacements in the struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(&measure.name, &displacements[j++]); + MPI_Get_address(&measure.info, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - // SLICE DEFINITION =================================================={{{1 + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - // ATTRIBUTES ============================================================ +}; +#+end_src + +*** Static utilities + +This section presents some functions which are useful to work with +slices and are inside the namespace created by the slice struct. + + +The function =subtupleBySlice= gives to every =Slice::Type= +its meaning in terms of the triples \( (a,b,c) \). + +Notice that since in general the relation +\( a < b < c \) holds (in our implementation), the case +of one-dimensional parametrizations =A=, =B= and =C= is well +defined. + +The function should only throw if there is an implementation +error where the =Slice::Type= enum has been expanded and this +function has not been updated accordingly. + +#+begin_src c++ :tangle (atrip-slice-h) +static +PartialTuple subtupleBySlice(ABCTuple abc, Type sliceType) { + switch (sliceType) { + case AB: return {abc[0], abc[1]}; + case BC: return {abc[1], abc[2]}; + case AC: return {abc[0], abc[2]}; + case CB: return {abc[2], abc[1]}; + case BA: return {abc[1], abc[0]}; + case CA: return {abc[2], abc[0]}; + case A: return {abc[0], 0}; + case B: return {abc[1], 0}; + case C: return {abc[2], 0}; + default: throw "Switch statement not exhaustive!"; + } +} +#+end_src + +In the context of cleaning up slices during the main loop, +it is important to check if a given slice has some slices +referencing to it in quality of recycled slices. + +This function should therefore return a vector of pointers +of slices referencing to the given slice's info, when +the length of the vector is zero, then there are no dangling +links. +#+begin_src c++ :tangle (atrip-slice-h) +static std::vector hasRecycledReferencingToIt + ( std::vector &slices + , Info const& info + ) { + std::vector result; + + for (auto& s: slices) + if ( s.info.recycling == info.type + && s.info.tuple == info.tuple + && s.info.state == Recycled + ) result.push_back(&s); + + return result; +} +#+end_src + +The rest of the coming functions are utilities in order to find in a vector +of slices a given slice by reference. Mostly they are merely convenience +wrappers to the standard library function =std::find_if=. + +They are named as =find<...>=, where =<...>= represents some condition +and must always return a reference to the found slice, i.e., =Slice&=. +=Atrip= relies on these functions to find the sought for slices, +therefore these functions will throw a =std::domain_error= if the +given slice could not be found. + +#+begin_src c++ :tangle (atrip-slice-h) +static Slice& findOneByType(std::vector &slices, Slice::Type type) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&type](Slice const& s) { + return type == s.info.type; + }); + WITH_CRAZY_DEBUG + WITH_RANK + << "\t__ looking for " << type << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error("Slice by type not found!"); + return *sliceIt; +} +#+end_src + +#+begin_src c++ :tangle (atrip-slice-h) +static Slice& +findRecycledSource (std::vector &slices, Slice::Info info) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&info](Slice const& s) { + return info.recycling == s.info.type + && info.tuple == s.info.tuple + && State::Recycled != s.info.state + ; + }); + + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find: recycling source of " + << pretty_print(info) << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice not found: " + + pretty_print(info) + + " rank: " + + pretty_print(Atrip::rank) + ); + WITH_RANK << "__slice__:find: " << pretty_print(sliceIt->info) << "\n"; + return *sliceIt; +} +#+end_src + +#+begin_src c++ :tangle (atrip-slice-h) +static Slice& findByTypeAbc + ( std::vector &slices + , Slice::Type type + , ABCTuple const& abc + ) { + const auto tuple = Slice::subtupleBySlice(abc, type); + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&type, &tuple](Slice const& s) { + return type == s.info.type + && tuple == s.info.tuple + ; + }); + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find:" << type << " and tuple " + << pretty_print(tuple) + << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice not found: " + + pretty_print(tuple) + + ", " + + pretty_print(type) + + " rank: " + + pretty_print(Atrip::rank) + ); + return *sliceIt; +} +#+end_src + +#+begin_src c++ :tangle (atrip-slice-h) +static Slice& findByInfo(std::vector &slices, + Slice::Info const& info) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&info](Slice const& s) { + // TODO: maybe implement comparison in Info struct + return info.type == s.info.type + && info.state == s.info.state + && info.tuple == s.info.tuple + && info.from.rank == s.info.from.rank + && info.from.source == s.info.from.source + ; + }); + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find:looking for " << pretty_print(info) << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice by info not found: " + + pretty_print(info)); + return *sliceIt; +} +#+end_src + +*** Attributes + +A slice object does not own data, it is just a container +or a pointer to data together with additional bookkeeping facilities. + +It includes an info structure with the information about the slice, +=Type=, =State= etc, which will be later communicated to other ranks. + +#+begin_src c++ :tangle (atrip-slice-h) Info info; - F *data; - MPI_Request request; - const size_t size; +#+end_src +A pointer to data is also necessary for the =Slice= but not necessary +to be communicated to other ranks. The =Slice= should never allocate +or deallocate itself the pointer. +#+begin_src c++ :tangle (atrip-slice-h) + F *data; +#+end_src + +An =MPI_Request= handle is also included so that the slices that are +to receive data through MPI can know which request they belong to. +#+begin_src c++ :tangle (atrip-slice-h) + MPI_Request request; +#+end_src + +For practical purposes in MPI calls, the number of elements in =data= is also included. +#+begin_src c++ :tangle (atrip-slice-h) + const size_t size; +#+end_src + +*** Member functions + +It is important to note that a ready slice should not be recycled from +any other slice, so that it can have access by itself to the data. +#+begin_src c++ :tangle (atrip-slice-h) void markReady() noexcept { info.state = Ready; info.recycling = Blank; } +#+end_src - /* - ,* This means that the data is there - ,*/ + +The following function asks wether or not +the slice has effectively been unwrapped or not, +i.e., wether or not the data are accessible and already +there. This can only happen in two ways, either +is the slice =Ready= or it is =SelfSufficient=, +i.e., the data pointed to was pre-distributed to the current node. +#+begin_src c++ :tangle (atrip-slice-h) bool isUnwrapped() const noexcept { return info.state == Ready || info.state == SelfSufficient ; } +#+end_src +The function =isUnwrappable= answers which slices can be unwrapped +potentially. Unwrapped slices can be unwrapped again idempotentially. +Also =Recycled= slices can be unwrapped, i.e. the slices pointed to by them +will be unwrapped. +The only other possibility is that the slice has been dispatched +in the past and can be unwrapped. The case where the state +is =Dispatched= is the canonical intuitive case where a real process +of unwrapping, i.e. waiting for the data to get through the network, +is done. +#+begin_src c++ :tangle (atrip-slice-h) bool isUnwrappable() const noexcept { return isUnwrapped() || info.state == Recycled @@ -393,19 +590,20 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds ; } +#+end_src - /* - ,* This function answers the question, which slices can be recycled. - ,* - ,* A slice can only be recycled if it is Fetch or Ready and has - ,* a valid datapointer. - ,* - ,* In particular, SelfSufficient are not recyclable, since it is easier - ,* just to create a SelfSufficient slice than deal with data dependencies. - ,* - ,* Furthermore, a recycled slice is not recyclable, if this is the case - ,* then it is either bad design or a bug. - ,*/ +The function =isRecylable= answers the question, which slices can be recycled. + +A slice can only be recycled if it is Fetch or Ready and has +a valid datapointer. + +In particular, SelfSufficient are not recyclable, since it is easier +just to create a SelfSufficient slice than deal with data dependencies. + +Furthermore, a recycled slice is not recyclable, if this is the case +then it is either bad design or a bug. + +#+begin_src c++ :tangle (atrip-slice-h) inline bool isRecyclable() const noexcept { return ( info.state == Dispatched || info.state == Ready @@ -414,21 +612,38 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds && hasValidDataPointer() ; } +#+end_src - /* - ,* This function describes if a slice has a valid data pointer. - ,* - ,* This is important to know if the slice has some data to it, also - ,* some structural checks are done, so that it should not be Acceptor - ,* or Blank, if this is the case then it is a bug. - ,*/ + +The function =hasValidDataPointer= describes if a slice has a valid +data pointer. + +This is important to know if the slice has some data to it, also +some structural checks are done, so that it should not be =Acceptor= +or =Blank=, if this is the case then it is a bug. + +#+begin_src c++ :tangle (atrip-slice-h) inline bool hasValidDataPointer() const noexcept { return data != nullptr && info.state != Acceptor && info.type != Blank ; } +#+end_src + +The function +=unwrapAndMarkReady= +calls the low-level MPI functions +in order to wait whenever the state of the slice is correct. +The main behaviour of the function should +- return if state is =Ready=, since then there is nothing to be done. +- throw if the state is not =Dispatched=, only a dispatched slice + can be unwrapped through MPI. +- throw if an MPI error happens. + + +#+begin_src c++ :tangle (atrip-slice-h) void unwrapAndMarkReady() { if (info.state == Ready) return; if (info.state != Dispatched) @@ -458,7 +673,10 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds << "\n"; #endif } +#+end_src +*** Epilog :noexport: +#+begin_src c++ :tangle (atrip-slice-h) Slice(size_t size_) : info({}) , data(nullptr) @@ -468,7 +686,11 @@ As an example, for the doubles amplitudes \( T^{ab}_{ij} \), one need two kinds }; // struct Slice +#+end_src +*** Debug :noexport: + +#+begin_src c++ :tangle (atrip-slice-h) std::ostream& operator<<(std::ostream& out, Slice::Location const& v) { // TODO: remove me out << "{.r(" << v.rank << "), .s(" << v.source << ")};"; @@ -528,6 +750,11 @@ namespace atrip { #+end_src ** The rank mapping + +This section introduces the concept of rank mapping, +which defines how slices will be allocated to every +rank. + #+begin_src c++ :tangle (atrip-rankmap-h) #pragma once diff --git a/include/atrip/Slice.hpp b/include/atrip/Slice.hpp index a7a5363..d7775c8 100644 --- a/include/atrip/Slice.hpp +++ b/include/atrip/Slice.hpp @@ -1,4 +1,4 @@ -// [[file:../../atrip.org::*The slice][The slice:1]] +// [[file:../../atrip.org::*Prolog][Prolog:1]] #pragma once #include #include @@ -14,401 +14,393 @@ namespace atrip { struct Slice { using F = double; -// The slice:1 ends here +// Prolog:1 ends here -// [[file:../../atrip.org::*The slice][The slice:2]] -// ASSOCIATED TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +// [[file:../../atrip.org::*Location][Location:1]] +struct Location { size_t rank; size_t source; }; +// Location:1 ends here - struct Location { size_t rank; size_t source; }; - - enum Type - { A = 10 - , B - , C - // Two-parameter slices - , AB = 20 - , BC - , AC - // for abci and the doubles - , CB - , BA - , CA - // The non-typed slice - , Blank = 404 - }; - - enum State { - // Fetch represents the state where a slice is to be fetched - // and has a valid data pointer that can be written to - Fetch = 0, - // Dispatches represents the state that an MPI call has been - // dispatched in order to get the data, but the data has not been - // yet unwrapped, the data might be there or we might have to wait. - Dispatched = 2, - // Ready means that the data pointer can be read from - Ready = 1, - // Self sufficient is a slice when its contents are located - // in the same rank that it lives, so that it does not have to - // fetch from no one else. - SelfSufficient = 911, - // Recycled means that this slice gets its data pointer from another - // slice, so it should not be written to - Recycled = 123, - // Acceptor means that the Slice can accept a new Slice, it is - // the counterpart of the Blank type, but for states - Acceptor = 405 +// [[file:../../atrip.org::*Type][Type:1]] +enum Type + { A = 10 + , B + , C + // Two-parameter slices + , AB = 20 + , BC + , AC + // for abci and the doubles + , CB + , BA + , CA + // The non-typed slice + , Blank = 404 }; +// Type:1 ends here - struct Info { - // which part of a,b,c the slice holds - PartialTuple tuple; - // The type of slice for the user to retrieve the correct one - Type type; - // What is the state of the slice - State state; - // Where the slice is to be retrieved - // NOTE: this can actually be computed from tuple - Location from; - // If the data are actually to be found in this other slice - Type recycling; +// [[file:../../atrip.org::*State][State:1]] +enum State { + Fetch = 0, + Dispatched = 2, + Ready = 1, + SelfSufficient = 911, + Recycled = 123, + Acceptor = 405 +}; +// State:1 ends here - Info() : tuple{0,0} - , type{Blank} - , state{Acceptor} - , from{0,0} - , recycling{Blank} - {} +// [[file:../../atrip.org::*The Info structure][The Info structure:1]] +struct Info { + // which part of a,b,c the slice holds + PartialTuple tuple; + // The type of slice for the user to retrieve the correct one + Type type; + // What is the state of the slice + State state; + // Where the slice is to be retrieved + Location from; + // If the data are actually to be found in this other slice + Type recycling; + + Info() : tuple{0,0} + , type{Blank} + , state{Acceptor} + , from{0,0} + , recycling{Blank} + {} +}; + +using Ty_x_Tu = std::pair< Type, PartialTuple >; +// The Info structure:1 ends here + +// [[file:../../atrip.org::*Name][Name:1]] +enum Name + { TA = 100 + , VIJKA = 101 + , VABCI = 200 + , TABIJ = 201 + , VABIJ = 202 }; +// Name:1 ends here - using Ty_x_Tu = std::pair< Type, PartialTuple >; +// [[file:../../atrip.org::*Database][Database:1]] +struct LocalDatabaseElement { + Slice::Name name; + Slice::Info info; +}; +// Database:1 ends here - // Names of the integrals that are considered in CCSD(T) - enum Name - { TA = 100 - , VIJKA = 101 - , VABCI = 200 - , TABIJ = 201 - , VABIJ = 202 - }; +// [[file:../../atrip.org::*Database][Database:2]] +using LocalDatabase = std::vector; +using Database = LocalDatabase; +// Database:2 ends here - // DATABASE ==========================================================={{{1 - struct LocalDatabaseElement { - Slice::Name name; - Slice::Info info; - }; - using LocalDatabase = std::vector; - using Database = LocalDatabase; +// [[file:../../atrip.org::*MPI Types][MPI Types:1]] +struct mpi { - - // STATIC METHODS =========================================================== - // - // They are useful to organize the structure of slices - - struct mpi { - - static MPI_Datatype vector(size_t n, MPI_Datatype const& DT) { - MPI_Datatype dt; - MPI_Type_vector(n, 1, 1, DT, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype sliceLocation () { - constexpr int n = 2; - // create a sliceLocation to measure in the current architecture - // the packing of the struct - Slice::Location measure; - MPI_Datatype dt; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] = {usizeDt(), usizeDt()}; - - // measure the displacements in the struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(&measure.rank, &displacements[j++]); - MPI_Get_address(&measure.source, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype enumDt() { return MPI_INT; } - static MPI_Datatype usizeDt() { return MPI_UINT64_T; } - - static MPI_Datatype sliceInfo () { - constexpr int n = 5; - MPI_Datatype dt; - Slice::Info measure; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] - = { vector(2, usizeDt()) - , enumDt() - , enumDt() - , sliceLocation() - , enumDt() - }; - - // create the displacements from the info measurement struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(measure.tuple.data(), &displacements[j++]); - MPI_Get_address(&measure.type, &displacements[j++]); - MPI_Get_address(&measure.state, &displacements[j++]); - MPI_Get_address(&measure.from, &displacements[j++]); - MPI_Get_address(&measure.recycling, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - static MPI_Datatype localDatabaseElement () { - constexpr int n = 2; - MPI_Datatype dt; - LocalDatabaseElement measure; - const std::vector lengths(n, 1); - const MPI_Datatype types[n] - = { enumDt() - , sliceInfo() - }; - - // measure the displacements in the struct - size_t j = 0; - MPI_Aint displacements[n]; - MPI_Get_address(&measure.name, &displacements[j++]); - MPI_Get_address(&measure.info, &displacements[j++]); - for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; - displacements[0] = 0; - - MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); - MPI_Type_commit(&dt); - return dt; - } - - }; - - static - PartialTuple subtupleBySlice(ABCTuple abc, Type sliceType) { - switch (sliceType) { - case AB: return {abc[0], abc[1]}; - case BC: return {abc[1], abc[2]}; - case AC: return {abc[0], abc[2]}; - case CB: return {abc[2], abc[1]}; - case BA: return {abc[1], abc[0]}; - case CA: return {abc[2], abc[0]}; - case A: return {abc[0], 0}; - case B: return {abc[1], 0}; - case C: return {abc[2], 0}; - default: throw "Switch statement not exhaustive!"; - } + static MPI_Datatype vector(size_t n, MPI_Datatype const& DT) { + MPI_Datatype dt; + MPI_Type_vector(n, 1, 1, DT, &dt); + MPI_Type_commit(&dt); + return dt; } + static MPI_Datatype sliceLocation () { + constexpr int n = 2; + // create a sliceLocation to measure in the current architecture + // the packing of the struct + Slice::Location measure; + MPI_Datatype dt; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] = {usizeDt(), usizeDt()}; - /** - * It is important here to return a reference to a Slice - * not to accidentally copy the associated buffer of the slice. - */ - static Slice& findOneByType(std::vector &slices, Slice::Type type) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&type](Slice const& s) { - return type == s.info.type; - }); - WITH_CRAZY_DEBUG - WITH_RANK - << "\t__ looking for " << type << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error("Slice by type not found!"); - return *sliceIt; - } + // measure the displacements in the struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(&measure.rank, &displacements[j++]); + MPI_Get_address(&measure.source, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - /* - * Check if an info has - * - */ - static std::vector hasRecycledReferencingToIt - ( std::vector &slices - , Info const& info - ) { - std::vector result; + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - for (auto& s: slices) - if ( s.info.recycling == info.type - && s.info.tuple == info.tuple - && s.info.state == Recycled - ) result.push_back(&s); + static MPI_Datatype enumDt() { return MPI_INT; } + static MPI_Datatype usizeDt() { return MPI_UINT64_T; } - return result; - } + static MPI_Datatype sliceInfo () { + constexpr int n = 5; + MPI_Datatype dt; + Slice::Info measure; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] + = { vector(2, usizeDt()) + , enumDt() + , enumDt() + , sliceLocation() + , enumDt() + }; - static Slice& - findRecycledSource (std::vector &slices, Slice::Info info) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&info](Slice const& s) { - return info.recycling == s.info.type - && info.tuple == s.info.tuple - && State::Recycled != s.info.state - ; - }); + // create the displacements from the info measurement struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(measure.tuple.data(), &displacements[j++]); + MPI_Get_address(&measure.type, &displacements[j++]); + MPI_Get_address(&measure.state, &displacements[j++]); + MPI_Get_address(&measure.from, &displacements[j++]); + MPI_Get_address(&measure.recycling, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find: recycling source of " - << pretty_print(info) << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice not found: " - + pretty_print(info) - + " rank: " - + pretty_print(Atrip::rank) - ); - WITH_RANK << "__slice__:find: " << pretty_print(sliceIt->info) << "\n"; - return *sliceIt; - } + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - static Slice& findByTypeAbc - ( std::vector &slices - , Slice::Type type - , ABCTuple const& abc - ) { - const auto tuple = Slice::subtupleBySlice(abc, type); - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&type, &tuple](Slice const& s) { - return type == s.info.type - && tuple == s.info.tuple - ; - }); - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find:" << type << " and tuple " - << pretty_print(tuple) - << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice not found: " - + pretty_print(tuple) - + ", " - + pretty_print(type) - + " rank: " - + pretty_print(Atrip::rank) - ); - return *sliceIt; - } + static MPI_Datatype localDatabaseElement () { + constexpr int n = 2; + MPI_Datatype dt; + LocalDatabaseElement measure; + const std::vector lengths(n, 1); + const MPI_Datatype types[n] + = { enumDt() + , sliceInfo() + }; - static Slice& findByInfo(std::vector &slices, - Slice::Info const& info) { - const auto sliceIt - = std::find_if(slices.begin(), slices.end(), - [&info](Slice const& s) { - // TODO: maybe implement comparison in Info struct - return info.type == s.info.type - && info.state == s.info.state - && info.tuple == s.info.tuple - && info.from.rank == s.info.from.rank - && info.from.source == s.info.from.source - ; - }); - WITH_CRAZY_DEBUG - WITH_RANK << "__slice__:find:looking for " << pretty_print(info) << "\n"; - if (sliceIt == slices.end()) - throw std::domain_error( "Slice by info not found: " - + pretty_print(info)); - return *sliceIt; - } + // measure the displacements in the struct + size_t j = 0; + MPI_Aint displacements[n]; + MPI_Get_address(&measure.name, &displacements[j++]); + MPI_Get_address(&measure.info, &displacements[j++]); + for (size_t i = 1; i < n; i++) displacements[i] -= displacements[0]; + displacements[0] = 0; - // SLICE DEFINITION =================================================={{{1 + MPI_Type_create_struct(n, lengths.data(), displacements, types, &dt); + MPI_Type_commit(&dt); + return dt; + } - // ATTRIBUTES ============================================================ - Info info; - F *data; - MPI_Request request; - const size_t size; +}; +// MPI Types:1 ends here - void markReady() noexcept { - info.state = Ready; - info.recycling = Blank; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:1]] +static +PartialTuple subtupleBySlice(ABCTuple abc, Type sliceType) { + switch (sliceType) { + case AB: return {abc[0], abc[1]}; + case BC: return {abc[1], abc[2]}; + case AC: return {abc[0], abc[2]}; + case CB: return {abc[2], abc[1]}; + case BA: return {abc[1], abc[0]}; + case CA: return {abc[2], abc[0]}; + case A: return {abc[0], 0}; + case B: return {abc[1], 0}; + case C: return {abc[2], 0}; + default: throw "Switch statement not exhaustive!"; + } +} +// Static utilities:1 ends here - /* - * This means that the data is there - */ - bool isUnwrapped() const noexcept { - return info.state == Ready - || info.state == SelfSufficient - ; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:2]] +static std::vector hasRecycledReferencingToIt + ( std::vector &slices + , Info const& info + ) { + std::vector result; - bool isUnwrappable() const noexcept { - return isUnwrapped() - || info.state == Recycled - || info.state == Dispatched - ; - } + for (auto& s: slices) + if ( s.info.recycling == info.type + && s.info.tuple == info.tuple + && s.info.state == Recycled + ) result.push_back(&s); - inline bool isDirectlyFetchable() const noexcept { - return info.state == Ready || info.state == Dispatched; - } + return result; +} +// Static utilities:2 ends here - void free() noexcept { - info.tuple = {0, 0}; - info.type = Blank; - info.state = Acceptor; - info.from = {0, 0}; - info.recycling = Blank; - data = nullptr; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:3]] +static Slice& findOneByType(std::vector &slices, Slice::Type type) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&type](Slice const& s) { + return type == s.info.type; + }); + WITH_CRAZY_DEBUG + WITH_RANK + << "\t__ looking for " << type << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error("Slice by type not found!"); + return *sliceIt; +} +// Static utilities:3 ends here - inline bool isFree() const noexcept { - return info.tuple == PartialTuple{0, 0} - && info.type == Blank - && info.state == Acceptor - && info.from.rank == 0 - && info.from.source == 0 - && info.recycling == Blank - && data == nullptr - ; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:4]] +static Slice& +findRecycledSource (std::vector &slices, Slice::Info info) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&info](Slice const& s) { + return info.recycling == s.info.type + && info.tuple == s.info.tuple + && State::Recycled != s.info.state + ; + }); + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find: recycling source of " + << pretty_print(info) << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice not found: " + + pretty_print(info) + + " rank: " + + pretty_print(Atrip::rank) + ); + WITH_RANK << "__slice__:find: " << pretty_print(sliceIt->info) << "\n"; + return *sliceIt; +} +// Static utilities:4 ends here - /* - * This function answers the question, which slices can be recycled. - * - * A slice can only be recycled if it is Fetch or Ready and has - * a valid datapointer. - * - * In particular, SelfSufficient are not recyclable, since it is easier - * just to create a SelfSufficient slice than deal with data dependencies. - * - * Furthermore, a recycled slice is not recyclable, if this is the case - * then it is either bad design or a bug. - */ - inline bool isRecyclable() const noexcept { - return ( info.state == Dispatched - || info.state == Ready - || info.state == Fetch - ) - && hasValidDataPointer() - ; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:5]] +static Slice& findByTypeAbc + ( std::vector &slices + , Slice::Type type + , ABCTuple const& abc + ) { + const auto tuple = Slice::subtupleBySlice(abc, type); + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&type, &tuple](Slice const& s) { + return type == s.info.type + && tuple == s.info.tuple + ; + }); + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find:" << type << " and tuple " + << pretty_print(tuple) + << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice not found: " + + pretty_print(tuple) + + ", " + + pretty_print(type) + + " rank: " + + pretty_print(Atrip::rank) + ); + return *sliceIt; +} +// Static utilities:5 ends here - /* - * This function describes if a slice has a valid data pointer. - * - * This is important to know if the slice has some data to it, also - * some structural checks are done, so that it should not be Acceptor - * or Blank, if this is the case then it is a bug. - */ - inline bool hasValidDataPointer() const noexcept { - return data != nullptr - && info.state != Acceptor - && info.type != Blank - ; - } +// [[file:../../atrip.org::*Static utilities][Static utilities:6]] +static Slice& findByInfo(std::vector &slices, + Slice::Info const& info) { + const auto sliceIt + = std::find_if(slices.begin(), slices.end(), + [&info](Slice const& s) { + // TODO: maybe implement comparison in Info struct + return info.type == s.info.type + && info.state == s.info.state + && info.tuple == s.info.tuple + && info.from.rank == s.info.from.rank + && info.from.source == s.info.from.source + ; + }); + WITH_CRAZY_DEBUG + WITH_RANK << "__slice__:find:looking for " << pretty_print(info) << "\n"; + if (sliceIt == slices.end()) + throw std::domain_error( "Slice by info not found: " + + pretty_print(info)); + return *sliceIt; +} +// Static utilities:6 ends here - void unwrapAndMarkReady() { +// [[file:../../atrip.org::*Attributes][Attributes:1]] +Info info; +// Attributes:1 ends here + +// [[file:../../atrip.org::*Attributes][Attributes:2]] +F *data; +// Attributes:2 ends here + +// [[file:../../atrip.org::*Attributes][Attributes:3]] +MPI_Request request; +// Attributes:3 ends here + +// [[file:../../atrip.org::*Attributes][Attributes:4]] +const size_t size; +// Attributes:4 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:1]] +void markReady() noexcept { + info.state = Ready; + info.recycling = Blank; +} +// Member functions:1 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:2]] +bool isUnwrapped() const noexcept { + return info.state == Ready + || info.state == SelfSufficient + ; +} +// Member functions:2 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:3]] +bool isUnwrappable() const noexcept { + return isUnwrapped() + || info.state == Recycled + || info.state == Dispatched + ; +} + +inline bool isDirectlyFetchable() const noexcept { + return info.state == Ready || info.state == Dispatched; +} + +void free() noexcept { + info.tuple = {0, 0}; + info.type = Blank; + info.state = Acceptor; + info.from = {0, 0}; + info.recycling = Blank; + data = nullptr; +} + +inline bool isFree() const noexcept { + return info.tuple == PartialTuple{0, 0} + && info.type == Blank + && info.state == Acceptor + && info.from.rank == 0 + && info.from.source == 0 + && info.recycling == Blank + && data == nullptr + ; +} +// Member functions:3 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:4]] +inline bool isRecyclable() const noexcept { + return ( info.state == Dispatched + || info.state == Ready + || info.state == Fetch + ) + && hasValidDataPointer() + ; +} +// Member functions:4 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:5]] +inline bool hasValidDataPointer() const noexcept { + return data != nullptr + && info.state != Acceptor + && info.type != Blank + ; +} +// Member functions:5 ends here + +// [[file:../../atrip.org::*Member functions][Member functions:6]] +void unwrapAndMarkReady() { if (info.state == Ready) return; if (info.state != Dispatched) throw @@ -437,17 +429,20 @@ struct Slice { << "\n"; #endif } +// Member functions:6 ends here - Slice(size_t size_) - : info({}) - , data(nullptr) - , size(size_) - {} +// [[file:../../atrip.org::*Epilog][Epilog:1]] +Slice(size_t size_) + : info({}) + , data(nullptr) + , size(size_) + {} - }; // struct Slice - +}; // struct Slice +// Epilog:1 ends here +// [[file:../../atrip.org::*Debug][Debug:1]] std::ostream& operator<<(std::ostream& out, Slice::Location const& v) { // TODO: remove me out << "{.r(" << v.rank << "), .s(" << v.source << ")};"; @@ -464,4 +459,4 @@ std::ostream& operator<<(std::ostream& out, Slice::Info const& i) { } } // namespace atrip -// The slice:2 ends here +// Debug:1 ends here