BELFEM 0.9.0
Berkeley Lab Finite Element Framework
Loading...
Searching...
No Matches
graphtools.hpp
Go to the documentation of this file.
1/*
2 * BELFEM -- The Berkeley Lab Finite Element Framework
3 * Copyright (c) 2026, The Regents of the University of California, through
4 * Lawrence Berkeley National Laboratory (subject to receipt of any required
5 * approvals from the U.S. Dept. of Energy). All rights reserved.
6 *
7 * Developers: Christian Messe, Gregory Giard
8 *
9 * See the top-level LICENSE file for the complete license and disclaimer.
10 */
11
12#ifndef BELFEM_GRAPHTOOLS_HPP
13#define BELFEM_GRAPHTOOLS_HPP
14
15#include "typedefs.hpp"
16#include "cl_Cell.hpp"
17#include "cl_DynamicBitset.hpp"
18#include "cl_Vector.hpp"
19#include "cl_Graph_Vertex.hpp"
21#include "commtools.hpp"
22
23
24namespace belfem
25{
26 namespace graph
27 {
28
29//------------------------------------------------------------------------------
30
31 template< typename T >
32 void
34 Graph & aGraph,
35 const Vector< T > & aPermutation )
36 {
37 // Apply the permutation to the graphx
38 index_t tCount = 0;
39 for( Vertex * tVertex : aGraph )
40 {
41 tVertex->set_index( static_cast< index_t> ( aPermutation( tCount++ ) ) );
42 }
43
44 // Sort the graph by the new indices
45 sort( aGraph, opVertexIndex );
46
47 DynamicBitset tBitset( aGraph.size() );
48
49 Cell< index_t > tIndices ;
50 for ( Vertex * tVertex : aGraph )
51 {
52 tBitset.reset();
53 for ( uint k=0; k<tVertex->number_of_vertices(); ++k )
54 {
55 tBitset.set( tVertex->vertex( k )->index() );
56 }
57 if ( tVertex->is_flagged() )
58 {
59 tBitset.set( tVertex->index() );
60 }
61 tBitset.where( tIndices );
62 tVertex->reset_vertex_container();
63 tVertex->init_vertex_container( tIndices.size() );
64 for ( index_t k : tIndices )
65 {
66 tVertex->insert_vertex( aGraph( k ) );
67 }
68 }
69 }
70
71//------------------------------------------------------------------------------
72
73 template< typename T >
74 void
76 Graph & aGraph,
77 Vector< T > & aVertices,
78 Vector< T > & aEdges )
79 {
80 T tNumVertices = static_cast< T >( aGraph.size() );
81
82 // First pass: ensure continuous indices
83 T tCount = 0;
84
85 for( Vertex * tVertex : aGraph )
86 {
87 tVertex->set_index( tCount++ );
88 }
89
90 // Second pass: count total edges. Self-loops are dropped here and
91 // in the fill below: a graph built from a matrix carries one per
92 // diagonal entry, and METIS_NodeNDP corrupts its heap on them
93 // ( reproduced 2026-09-04, FM_2WayNodeRefine1Sided → rpqDestroy ).
94 // METIS and SCOTCH define their input as loop-free; ParMETIS passes
95 // loops through unchecked. The Graph itself keeps them: DistMatrix
96 // builds the permuted SpMatrix from the Graph and needs the diagonal
97 tCount = 0;
98 for( Vertex * tVertex : aGraph )
99 {
100 for( uint k = 0; k < tVertex->number_of_vertices(); ++k )
101 {
102 if( tVertex->vertex( k ) != tVertex )
103 {
104 ++tCount;
105 }
106 }
107 }
108
109 // Allocate METIS arrays
110 aVertices.set_size( tNumVertices + 1, 0 );
111 // Always allocate at least 1 element to avoid NULL pointers
112 if( tCount > 0 )
113 {
114 aEdges.set_size( tCount, 0 );
115 }
116 else
117 {
118 aEdges.set_size( 1, 0 );
119 }
120
121 // Build the adjacency structure (CSR format)
122 tCount = 0;
123
124 for( Vertex * tVertex : aGraph )
125 {
126 aVertices( tVertex->index() ) = tCount;
127
128 for( uint k = 0; k < tVertex->number_of_vertices(); ++k )
129 {
130 if( tVertex->vertex( k ) != tVertex )
131 {
132 aEdges( tCount++ ) = static_cast< T >( tVertex->vertex( k )->index() );
133 }
134 }
135 }
136 aVertices( tNumVertices ) = tCount;
137 }
138
148 inline void
149 block_distribution( Graph & aGraph, const proc_t aCommSize )
150 {
151 BELFEM_ASSERT( aCommSize > 0, "block_distribution: comm size %d", ( int ) aCommSize );
152
153 const index_t tN = aGraph.size();
154 const index_t tP = static_cast< index_t >( aCommSize );
155 const index_t tDiv = tN / tP;
156 const index_t tSplit = tP - ( tN % tP );
157
158 index_t tCount = 0;
159 for ( index_t p = 0; p < tSplit; ++p )
160 {
161 for ( index_t k = 0; k < tDiv; ++k )
162 {
163 aGraph( tCount++ )->set_owner( static_cast< proc_t >( p ) );
164 }
165 }
166 for ( index_t p = tSplit; p < tP; ++p )
167 {
168 for ( index_t k = 0; k < tDiv + 1; ++k )
169 {
170 aGraph( tCount++ )->set_owner( static_cast< proc_t >( p ) );
171 }
172 }
173 BELFEM_ASSERT( tCount == tN, "block_distribution: assigned %lu of %lu vertices",
174 ( long unsigned int ) tCount, ( long unsigned int ) tN );
175 }
176
177 template < typename T >
178 void
180 Graph & aGraph,
181 Vector< T > & aDistribution,
182 Cell< Vector< T > > & aVertices,
183 Cell< Vector< T > > & aEdges )
184 {
185 proc_t tCommSize = comm_size();
186 BELFEM_ASSERT( comm_rank() == 0, "This build_pargraph_adjacency() can only be called by root" );
187
188 // first we need to reorder the graph based on its owner
189 Vector< T > tCount( tCommSize, 0 );
190 for ( Vertex * tVertex : aGraph )
191 {
192 tVertex->unflag();
193
194 // unowned vertices (e.g. circuit dofs) go to root. Two sentinels are in
195 // use: the Kernel's comm_size() marker and the gNoOwner default.
196 proc_t tOwner = tVertex->owner();
197 if ( tOwner == tCommSize || tOwner == gNoOwner )
198 {
199 tOwner = 0;
200 tVertex->set_owner( tOwner );
201 }
202 BELFEM_ERROR( tOwner >= 0 && tOwner < tCommSize,
203 "Vertex %lu has owner %d outside [0, %d)",
204 ( long unsigned int ) tVertex->id(), ( int ) tOwner, ( int ) tCommSize );
205
206 ++tCount( tOwner );
207 }
208
209 aDistribution.set_size( comm_size()+1, 0 );
210 for ( proc_t p = 0; p < comm_size(); ++p )
211 {
212 aDistribution( p+1 ) = aDistribution( p ) + tCount( p );
213 }
214
215 // now we need to reorder the graph based on its owner
216 Graph tGraph( aGraph.size(), nullptr );
217 tCount.fill( 0 );
218 for ( Vertex * tVertex : aGraph )
219 {
220 tGraph( aDistribution( tVertex->owner() ) + tCount( tVertex->owner() )++ ) = tVertex;
221 }
222 aGraph = std::move( tGraph );
223 tGraph.clear();
224#if defined( DEBUG )
225 for ( Vertex * tVertex : aGraph )
226 {
227 for ( uint k = 0; k < tVertex->number_of_vertices(); ++k )
228 {
229 tVertex->vertex( k )->set_index( gNoIndex );
230 }
231 tVertex->set_index( gNoIndex );
232 }
233#endif
234
235 // next, we make sure that the indices are consecutive
236 index_t tIndex = 0;
237 for ( Vertex * tVertex : aGraph )
238 {
239 tVertex->set_index( tIndex++ );
240 }
241
242#if defined( DEBUG )
243 for ( Vertex * tVertex : aGraph )
244 {
245 for ( uint k = 0; k < tVertex->number_of_vertices(); ++k )
246 {
247 BELFEM_ERROR( tVertex->vertex( k )->index() != gNoIndex,
248 "Vertex index of %lu (owner %u) is not set",
249 ( long unsigned int ) tVertex->vertex( k )->id() ,
250 ( uint ) tVertex->vertex( k)->owner() );
251 }
252 }
253#endif
254
255 // Count edges per processor, self-loops dropped ( see
256 // build_graph_adjacency for why )
257 Vector< T > tNNZ( tCommSize, 0 );
258 for ( Vertex * tVertex : aGraph )
259 {
260 for ( uint k = 0; k < tVertex->number_of_vertices(); ++k )
261 {
262 if ( tVertex->vertex( k ) != tVertex )
263 {
264 ++tNNZ( tVertex->owner() );
265 }
266 }
267 }
268
269 aVertices.set_size( tCommSize, {} );
270 aEdges.set_size( tCommSize, {} );
271
272 for ( proc_t p = 0; p < tCommSize; ++p )
273 {
274 aVertices( p ).set_size( tCount( p ) + 1 );
275
276 // Always allocate at least 1 element to avoid NULL pointers in
277 // ParMETIS. Branch on the EDGE count: a slice whose vertices
278 // carry only self-loops has vertices but, after the loop drop,
279 // no edges, and must get the placeholder too
280 if ( tNNZ( p ) > 0 )
281 {
282 aEdges( p ).set_size( tNNZ( p ) );
283 }
284 else
285 {
286 aEdges( p ).set_size( 1, 0 );
287 }
288 }
289
290 Vector< T > tXcount( tCommSize, 0 );
291 Vector< T > tAcount( tCommSize, 0 );
292
293 for( Vertex * tVertex : aGraph )
294 {
295 proc_t tOwner = tVertex->owner();
296 aVertices( tOwner )( tXcount( tOwner )++ ) = tAcount( tOwner );
297
298 for ( uint k=0; k<tVertex->number_of_vertices(); ++k )
299 {
300 if ( tVertex->vertex( k ) != tVertex )
301 {
302 aEdges( tOwner )( tAcount( tOwner )++ ) = static_cast< T >( tVertex->vertex( k )->index() );
303 }
304 }
305 }
306
307 BELFEM_ASSERT( tAcount == tNNZ, "The number of non-zeros in the adjacency matrix is not correct" );
308
309 for ( proc_t p = 0; p < tCommSize; ++p )
310 {
311 aVertices( p )( tCount( p ) ) = tNNZ( p );
312 }
313 }
314
315 }
316}
317#endif //BELFEM_GRAPHTOOLS_HPP
#define BELFEM_ERROR(aCheck,...)
Definition assert.hpp:264
#define BELFEM_ASSERT(aCheck,...)
Definition assert.hpp:244
size_t size() const
return the size of the Cell
Definition cl_Cell.hpp:181
void clear()
clear the memory
Definition cl_Cell.hpp:240
Runtime-sized bitset; one bit per flag, packed into 64-bit words.
Definition cl_DynamicBitset.hpp:33
void where(Cell< index_t > &aBits, const bool aAssumeSparse=true) const
Definition cl_DynamicBitset.hpp:704
void reset(const index_t aPos)
Resets (clears) the bit at the given position.
Definition cl_DynamicBitset.hpp:546
void set(const index_t aPos)
Sets the bit at the given position to 1.
Definition cl_DynamicBitset.hpp:524
Column vector.
Definition cl_BZ_Vector.hpp:41
void fill(const T &aValue)
write value into all entries of the vector
Definition cl_AR_Vector.hpp:226
void set_size(const size_t aNumRows)
change the size of the vector
Definition cl_AR_Vector.hpp:237
Graph node with an adjacency list; the vertex type the graph algorithms operate on.
Definition cl_Graph_Vertex.hpp:32
Definition cl_Graph_Vertex.cpp:21
void apply_graph_permutation(Graph &aGraph, const Vector< T > &aPermutation)
Definition graphtools.hpp:33
void block_distribution(Graph &aGraph, const proc_t aCommSize)
assign owners to an ownerless graph in contiguous blocks of graph order: the first aCommSize - ( N mo...
Definition graphtools.hpp:149
void build_graph_adjacency(Graph &aGraph, Vector< T > &aVertices, Vector< T > &aEdges)
Definition graphtools.hpp:75
void build_pargraph_adjacency(Graph &aGraph, Vector< T > &aDistribution, Cell< Vector< T > > &aVertices, Cell< Vector< T > > &aEdges)
Definition graphtools.hpp:179
void sort(Graph &aGraph)
Definition fn_Graph_sort.cpp:19
USER GUIDES:
Definition cl_Capacitor.cpp:16
proc_t comm_rank()
Returns the rank of the current process in the communicator.
Definition commtools.cpp:30
Cell< graph::Vertex * > Graph
Definition cl_Graph_Vertex.hpp:329
unsigned int uint
Definition typedefs.hpp:30
int proc_t
Definition commtypes.hpp:29
constexpr index_t gNoIndex
Definition typedefs.hpp:57
struct belfem::OpVertexIndex opVertexIndex
constexpr proc_t gNoOwner
Definition typedefs.hpp:59
uint32_t index_t
Definition typedefs.hpp:52
proc_t comm_size()
Returns the number of processes in the communicator.
Definition commtools.cpp:22