Nektar++
SubStructuredGraph.cpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: SubStructuredGraph.cpp
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: a collection of classes that facilitates the implementation
32// of the multi-level static condensation routines
33//
34///////////////////////////////////////////////////////////////////////////////
35
38
39#include <algorithm>
40#include <boost/algorithm/string/replace.hpp>
41#include <boost/config.hpp>
42#include <boost/graph/adjacency_list.hpp>
43#include <boost/graph/bandwidth.hpp>
44#include <boost/graph/cuthill_mckee_ordering.hpp>
45#include <boost/graph/properties.hpp>
46#include <iomanip>
47#include <iostream>
48
49using std::cout;
50using std::endl;
51using std::max;
52
53#ifdef NEKTAR_USE_SCOTCH
54#ifdef NEKTAR_USE_MPI
55#include <ptscotch.h>
56#else
57#include <scotch.h>
58#endif
59
60#define SCOTCH_CALL(scotchFunc, args) \
61 { \
62 ASSERTL0(scotchFunc args == 0, \
63 std::string("Error in Scotch calling function ") + \
64 std::string(#scotchFunc)); \
65 }
66#endif
67
69{
71{
72}
73
74PatchMap::PatchMap(const int nvals)
75{
80}
81
83{
84}
85
86void PatchMap::SetPatchMap(const int n, const int patchId, const int dofId,
87 const unsigned int bndPatch, const NekDouble sign)
88{
89 m_patchId[n] = patchId;
90 m_dofId[n] = dofId;
91 m_bndPatch[n] = bndPatch;
92 m_sign[n] = sign;
93}
94
96 Array<OneD, const unsigned int> numLocalBndCondPerPatch,
97 Array<OneD, const unsigned int> numLocalIntCondPerPatch)
98{
99 int npatch = numLocalBndCondPerPatch.size();
100
101 Array<OneD, int> bndoffset(npatch + 1);
102 Array<OneD, int> intoffset(npatch + 1);
103
104 bndoffset[0] = intoffset[0] = 0;
105 for (int i = 1; i <= npatch; ++i)
106 {
107 bndoffset[i] = bndoffset[i - 1] + numLocalBndCondPerPatch[i - 1];
108 intoffset[i] = intoffset[i - 1] + numLocalIntCondPerPatch[i - 1];
109 }
110
112
113 for (int i = 0; i < m_dofId.size(); ++i)
114 {
115 if (m_bndPatch[i] == true)
116 {
117 m_newLevelMap[i] = bndoffset[m_patchId[i]] + m_dofId[i];
118 }
119 else
120 {
121 m_newLevelMap[i] =
122 bndoffset[npatch] + intoffset[m_patchId[i]] + m_dofId[i];
123 }
124 }
125}
126
128 MultiLevelBisectedGraphSharedPtr oldLevel, const int nPartition)
129{
130 m_daughterGraphs.push_back(oldLevel);
132}
133
135 : m_BndDofs(MemoryManager<SubGraph>::AllocateSharedPtr(nBndDofs))
136{
137}
138
140{
141}
142
144{
145 int returnval = 0;
146
147 for (auto &g : m_daughterGraphs)
148 {
149 returnval += g->GetTotDofs();
150 }
151
152 returnval += m_BndDofs->GetNverts();
153 return returnval;
154}
155
157{
158 static int level = 0;
159 static int offset = 0;
160 level++;
161
162 for (auto &g : m_daughterGraphs)
163 {
164 g->SetGlobalNumberingOffset();
165 }
166
167 m_BndDofs->SetIdOffset(offset);
168 offset += m_BndDofs->GetNverts();
169
170 level--;
171 if (level == 0)
172 {
173 offset = 0;
174 }
175}
176
178{
179 static int level = 0;
180 level++;
181 cout << "LEVEL " << level << " " << m_BndDofs->GetNverts() << endl;
182
183 for (auto &g : m_daughterGraphs)
184 {
185 g->DumpNBndDofs();
186 }
187
188 level--;
189}
190
192 std::vector<SubGraphSharedPtr> &leaves) const
193{
194 int cnt = 0;
195
196 for (auto &g : m_daughterGraphs)
197 {
198 g->CollectLeaves(leaves);
199 cnt++;
200 }
201
202 if (cnt == 0)
203 {
205 leaves.push_back(leave);
206 }
207}
208
210{
211 int returnval;
212 static int level = 0;
213 static int nLeaves = 0;
214 level++;
215
216 if (level == 1 && m_daughterGraphs.size() == 0)
217 {
218 level = 0;
219 nLeaves = 0;
220 return 0;
221 }
222
223 for (auto it = m_daughterGraphs.begin(); it != m_daughterGraphs.end();)
224 {
225 auto g = *it;
226 if (g->GetNdaughterGraphs() == 0 &&
227 g->GetBndDofsGraph()->GetNverts() == 0)
228 {
229 it = m_daughterGraphs.erase(it);
230 nLeaves++;
231 }
232 else
233 {
234 g->CutEmptyLeaves();
235 ++it;
236 }
237 }
238
239 returnval = nLeaves;
240
241 level--;
242 if (level == 0)
243 {
244 nLeaves = 0;
245 }
246
247 return returnval;
248}
249
251{
252 int returnval;
253 static int level = 0;
254 static int nLeaves = 0;
255 level++;
256
257 if (level == 1 && m_daughterGraphs.size() == 0)
258 {
259 level = 0;
260 nLeaves = 0;
261 return 0;
262 }
263
264 for (auto it = m_daughterGraphs.begin(); it != m_daughterGraphs.end();)
265 {
266 auto g = *it;
267 if (g->GetNdaughterGraphs() == 0)
268 {
269 it = m_daughterGraphs.erase(it);
270 nLeaves++;
271 }
272 else
273 {
274 g->CutLeaves();
275 ++it;
276 }
277 }
278
279 returnval = nLeaves;
280
281 level--;
282 if (level == 0)
283 {
284 nLeaves = 0;
285 }
286
287 return returnval;
288}
289
291 : m_IntBlocks(), m_daughterGraph()
292{
293 // This constructor should only be used in the very special case
294 // when there is a graph consisting of nVerts vertices but without
295 // any connectivity whatsowever (i.e. no edges)
296
297 SubGraphSharedPtr subgraph;
298 for (int i = 0; i < nVerts; i++)
299 {
301 m_IntBlocks.push_back(subgraph);
302 }
303}
304
306 MultiLevelBisectedGraphSharedPtr graph, int nPartition, bool globaloffset)
307 : m_IntBlocks(), m_daughterGraph()
308{
309 int ncuts;
310
311 if (nPartition > 0)
312 {
314 graph, nPartition);
315 }
316
317 if (globaloffset)
318 {
319 graph->SetGlobalNumberingOffset();
320 }
321
322 graph->CutEmptyLeaves();
323 graph->CollectLeaves(m_IntBlocks);
324 ncuts = graph->CutLeaves();
325
326 if (ncuts)
327 {
330 }
331}
332
334{
335}
336
338{
339 static int nIntDofs = 0;
340 static int level = 0;
341 level++;
342
343 int returnval;
344
345 if (m_daughterGraph.get())
346 {
347 m_daughterGraph->GetTotDofs();
348 }
349
350 for (int i = 0; i < m_IntBlocks.size(); i++)
351 {
352 nIntDofs += m_IntBlocks[i]->GetNverts();
353 }
354
355 returnval = nIntDofs;
356
357 level--;
358 if (level == 0)
359 {
360 nIntDofs = 0;
361 }
362
363 return returnval;
364}
365
367{
368 static int level = 0;
369 level++;
370
371 cout << "LEVEL " << level << endl;
372 cout << "interior blocks" << endl;
373 for (int i = 0; i < m_IntBlocks.size(); i++)
374 {
375 cout << " " << i << "/" << m_IntBlocks.size() - 1 << ": "
376 << m_IntBlocks[i]->GetNverts() << ", "
377 << m_IntBlocks[i]->GetIdOffset() << endl;
378 }
379 cout << endl;
380
381 if (m_daughterGraph.get())
382 {
383 m_daughterGraph->Dump();
384 }
385
386 level--;
387}
388
390 Array<OneD, int> &perm, Array<OneD, int> &iperm) const
391{
392 int nDofs = GetTotDofs();
393
394 // Step 1: make a permutation array that goes from the current
395 // reordering in the bottom-up graph to an ordering where the
396 // interior dofs of the first (=bottom) level are ordered last,
397 // followed interior dofs of the second level, ...
398 Array<OneD, int> iperm1(nDofs);
399 SetBottomUpReordering(iperm1);
400
401 // Now, based upon the input permutation array, update the
402 // permutation arrays between the original ordering of the dofs
403 // (defined somewhere outside) and the final reordering as given by
404 // iperm1
405 for (int i = 0; i < nDofs; i++)
406 {
407 iperm[i] = iperm1[iperm[i]];
408 perm[iperm[i]] = i;
409 }
410}
411
413 Array<OneD, int> &iperm) const
414{
415 static int offset = 0;
416 static int level = 0;
417 level++;
418
419 if (m_daughterGraph.get())
420 {
421 m_daughterGraph->SetBottomUpReordering(iperm);
422 }
423
424 for (int i = 0; i < m_IntBlocks.size(); i++)
425 {
426 int GlobIdOffset = m_IntBlocks[i]->GetIdOffset();
427 m_IntBlocks[i]->SetIdOffset(offset);
428 for (int j = 0; j < m_IntBlocks[i]->GetNverts(); j++)
429 {
430 iperm[GlobIdOffset + j] = offset;
431 offset++;
432 }
433 }
434
435 level--;
436 if (level == 0)
437 {
438 offset = 0;
439 }
440}
441
443 const Array<OneD, const int> &wgts)
444{
445 static int offset = 0;
446 static int level = 0;
447 level++;
448
449 if (m_daughterGraph.get())
450 {
451 m_daughterGraph->ExpandGraphWithVertexWeights(wgts);
452 }
453
454 for (int i = 0; i < m_IntBlocks.size(); i++)
455 {
456 int OrigGlobIdOffset = m_IntBlocks[i]->GetIdOffset();
457 int newNverts = 0;
458
459 m_IntBlocks[i]->SetIdOffset(offset);
460
461 for (int j = 0; j < m_IntBlocks[i]->GetNverts(); j++)
462 {
463 newNverts += wgts[OrigGlobIdOffset + j];
464 offset += wgts[OrigGlobIdOffset + j];
465 }
466
467 m_IntBlocks[i]->SetNverts(newNverts);
468 }
469
470 // erase the blocks that do not have any vertices
471 m_IntBlocks.erase(
472 remove_if(m_IntBlocks.begin(), m_IntBlocks.end(), SubGraphWithoutVerts),
473 m_IntBlocks.end());
474
475 // remove the current level if there are no interior blocks
476 if (m_IntBlocks.size() == 0 && m_daughterGraph.get())
477 {
478 m_IntBlocks = m_daughterGraph->GetInteriorBlocks();
479 m_daughterGraph = m_daughterGraph->GetDaughterGraph();
480 }
481
482 level--;
483 if (level == 0)
484 {
485 offset = 0;
486 }
487}
488
490 const int leveltomask, Array<OneD, NekDouble> &maskarray) const
491{
492 static int level = 0;
493 level++;
494
495 if (level < leveltomask)
496 {
497 m_daughterGraph->MaskPatches(leveltomask, maskarray);
498 }
499 else if (level == leveltomask)
500 {
501 int GlobIdOffset;
502 int nVerts;
503 for (int i = 0; i < m_IntBlocks.size(); i++)
504 {
505 GlobIdOffset = m_IntBlocks[i]->GetIdOffset();
506 nVerts = m_IntBlocks[i]->GetNverts();
507 for (int j = 0; j < nVerts; j++)
508 {
509 maskarray[GlobIdOffset + j] = (NekDouble)i;
510 }
511 }
512 }
513 else
514 {
515 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
516 }
517
518 level--;
519}
520
522 const int whichlevel) const
523{
524 int returnval = -1;
525 static int level = 0;
526 level++;
527
528 if (level < whichlevel)
529 {
530 returnval = m_daughterGraph->GetNpatchesWithInterior(whichlevel);
531 }
532 else if (level == whichlevel)
533 {
534 returnval = m_IntBlocks.size();
535 }
536 else
537 {
538 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
539 }
540
541 level--;
542
543 return returnval;
544}
545
547 const int whichlevel, Array<OneD, unsigned int> &outarray) const
548{
549 static int level = 0;
550 level++;
551
552 if (level < whichlevel)
553 {
554 m_daughterGraph->GetNintDofsPerPatch(whichlevel, outarray);
555 }
556 else if (level == whichlevel)
557 {
558 ASSERTL1(outarray.size() >= m_IntBlocks.size(),
559 "Array dimension not sufficient");
560
561 for (int i = 0; i < m_IntBlocks.size(); i++)
562 {
563 outarray[i] = (unsigned int)m_IntBlocks[i]->GetNverts();
564 }
565 }
566 else
567 {
568 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
569 }
570
571 level--;
572}
573
575 const int patch) const
576{
577 int retval = -1;
578 static int level = 0;
579 level++;
580
581 if (level < whichlevel)
582 {
583 retval = m_daughterGraph->GetInteriorOffset(whichlevel, patch);
584 }
585 else if (level == whichlevel)
586 {
587 retval = m_IntBlocks[patch]->GetIdOffset();
588 }
589 else
590 {
591 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
592 }
593
594 level--;
595
596 return retval;
597}
598
600 const int whichlevel) const
601{
602 std::vector<SubGraphSharedPtr> returnval;
603 static int level = 0;
604 level++;
605
606 if (level < whichlevel)
607 {
608 returnval = m_daughterGraph->GetInteriorBlocks(whichlevel);
609 }
610 else if (level == whichlevel)
611 {
612 returnval = m_IntBlocks;
613 }
614 else
615 {
616 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
617 }
618
619 level--;
620 return returnval;
621}
622
623int BottomUpSubStructuredGraph::GetNumGlobalDofs(const int whichlevel) const
624{
625 int returnval = -1;
626 static int level = 0;
627 level++;
628
629 if (level < whichlevel)
630 {
631 returnval = m_daughterGraph->GetNumGlobalDofs(whichlevel);
632 }
633 else if (level == whichlevel)
634 {
635 returnval = m_IntBlocks[m_IntBlocks.size() - 1]->GetIdOffset() +
636 m_IntBlocks[m_IntBlocks.size() - 1]->GetNverts();
637 }
638 else
639 {
640 NEKERROR(ErrorUtil::efatal, "If statement should not arrive here");
641 }
642
643 level--;
644
645 return returnval;
646}
647
649{
650 int returnval = 0;
651 static int level = 0;
652 level++;
653
654 if (m_daughterGraph.get())
655 {
656 returnval = m_daughterGraph->GetNlevels();
657 }
658 returnval = max(returnval, level);
659
660 level--;
661
662 return returnval;
663}
664
666{
667 return g->GetNverts() == 0;
668}
669
670namespace
671{
672// the first template parameter (=OutEdgeList) is chosen to be of
673// type std::set as in the set up of the adjacency, a similar edge
674// might be created multiple times. And to prevent the definition
675// of parallell edges, we use std::set (=boost::setS) rather than
676// std::vector (=boost::vecS)
677typedef boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS>
678 BoostGraph;
679typedef boost::graph_traits<BoostGraph>::vertex_descriptor BoostVertex;
680} // namespace
681
682void CuthillMckeeReordering(const BoostGraph &graph, Array<OneD, int> &perm,
683 Array<OneD, int> &iperm)
684{
685 int nGraphVerts = boost::num_vertices(graph);
686
687 ASSERTL1(perm.size() >= nGraphVerts && iperm.size() >= nGraphVerts,
688 "Non-matching dimensions");
689
690 // Call boost::cuthill_mckee_ordering to reorder the graph-vertices
691 // using the reverse Cuthill-Mckee algorithm
692 std::vector<BoostVertex> reorderedVerts(nGraphVerts);
693 boost::cuthill_mckee_ordering(graph, reorderedVerts.rbegin());
694
695 // copy the reordering to the Arrays perm and iperm
696 for (int i = 0; i < nGraphVerts; i++)
697 {
698 perm[i] = reorderedVerts[i];
699 iperm[reorderedVerts[i]] = i;
700 }
701}
702
704 [[maybe_unused]] const BoostGraph &graph,
705 [[maybe_unused]] Array<OneD, int> &perm,
706 [[maybe_unused]] Array<OneD, int> &iperm,
707 [[maybe_unused]] BottomUpSubStructuredGraphSharedPtr &substructgraph,
708 [[maybe_unused]] std::set<int> partVerts, [[maybe_unused]] int mdswitch)
709{
710#ifndef NEKTAR_USE_SCOTCH
711 ASSERTL0(false, "Multi-level static condensation requires Nektar++"
712 " to be built with SCOTCH.");
713#else
714 int nGraphVerts = boost::num_vertices(graph);
715 int nGraphEdges = boost::num_edges(graph);
716
717 ASSERTL1(perm.size() >= nGraphVerts && iperm.size() >= nGraphVerts,
718 "Non-matching dimensions");
719
720 // We will now use Scotch to reorder the graph. For the purpose of
721 // multi-level static condensation, we will use a Scotch routine
722 // that partitions the graph recursively using a multi-level nested
723 // bisection algorithm. The name of this routine is
724 // SCOTCH_graphOrder and it was originally designed to reorder the
725 // DOFs in a matrix in order to minimise the fill-in when applying a
726 // factorisation technique (such as Cholesky). However, this
727 // reordering of DOFs also seems to be perfectly suited in the
728 // context of multilevel substructuring. Therefore, we will use this
729 // Scotch routine instead of the more well-known graph-partitioning
730 // routines.
731 if (nGraphEdges)
732 {
733 //
734 // Step 1: Convert boost graph to a graph in adjncy-list format
735 // as required by Scotch.
736 //
737 int acnt = 0, vcnt = 0, i, cnt;
738 int nPartition = partVerts.size();
739 int nNonPartition = nGraphVerts - partVerts.size();
740
741 Array<OneD, int> xadj(nNonPartition + 1, 0);
742 Array<OneD, int> adjncy(2 * nGraphEdges);
743 Array<OneD, int> initial_perm(nGraphVerts);
744 Array<OneD, int> iinitial_perm(nGraphVerts);
745 Array<OneD, int> perm_tmp(nNonPartition);
746 Array<OneD, int> iperm_tmp(nNonPartition);
747
748 // Perform an initial reordering of the vertices, so that
749 // partition nodes are at the end. This allows Scotch to
750 // partition the interior nodes from values starting at zero.
751 for (i = cnt = 0; i < nGraphVerts; ++i)
752 {
753 if (partVerts.count(i) == 0)
754 {
755 initial_perm[i] = cnt;
756 iinitial_perm[cnt++] = i;
757 }
758 }
759
760 for (i = 0; i < nGraphVerts; ++i)
761 {
762 if (partVerts.count(i) > 0)
763 {
764 initial_perm[i] = cnt;
765 iinitial_perm[cnt++] = i;
766 }
767 }
768
769 // Apply this reordering to the graph.
770 boost::property_map<BoostGraph, boost::vertex_index_t>::type index =
771 get(boost::vertex_index, graph);
772
773 // Now construct the adjaceny list using
774 // boost::adjacent_vertices.
775 auto verts = boost::vertices(graph);
776 for (auto vertit = verts.first; vertit != verts.second; ++vertit)
777 {
778 if (partVerts.count(index[*vertit]) > 0)
779 {
780 continue;
781 }
782
783 auto adjverts = boost::adjacent_vertices(*vertit, graph);
784 for (auto adjvertit = adjverts.first; adjvertit != adjverts.second;
785 ++adjvertit)
786 {
787 if (partVerts.count(index[*adjvertit]) > 0)
788 {
789 continue;
790 }
791 adjncy[acnt++] = initial_perm[*adjvertit];
792 }
793 xadj[++vcnt] = acnt;
794 }
795
796 //
797 // Step 2: pass the graph to Scotch and perform the nested
798 // dissection to obtain a separator tree.
799 //
800
801 // Pass the adjaceny graph into Scotch.
802 SCOTCH_Graph *scGraph = SCOTCH_graphAlloc();
803 ASSERTL0(scGraph != nullptr,
804 "Failed to allocate Scotch graph for substructuring.");
805
806 ASSERTL0(SCOTCH_graphInit(scGraph) == 0,
807 "Failed to initialise Scotch graph for substructuring.");
808
809 SCOTCH_CALL(SCOTCH_graphBuild,
810 (scGraph, 0, nNonPartition, &xadj[0], &xadj[1], nullptr,
811 nullptr, xadj[nNonPartition], &adjncy[0], nullptr));
812
813 // This horrible looking string defines the Scotch graph
814 // reordering strategy, which essentially does a nested
815 // dissection + compression. We take this almost directly from
816 // the SCOTCH_stratGraphOrderBuild function (defined in
817 // library_graph_order.c), but by specifying the string
818 // manually, we can replace the subdivision strategy to allow us
819 // to control the number of vertices used to determine whether
820 // to perform another dissection using the mdswitch
821 // parameter. The below is essentially equivalent to calling
822 // SCOTCH_stratGraphOrderBuild with the flags
823 // SCOTCH_STRATLEAFSIMPLE and SCOTCH_STRATSEPASIMPLE to make
824 // sure leaf nodes do not have any reordering applied to them.
825 std::string strat_str =
826 "c{rat=0.7,cpr=n{sep=/(<TSTS>)?m{rat=0.7,vert=100,low="
827 "h{pass=10},asc=b{width=3,bnd=f{bal=<BBAL>},"
828 "org=(|h{pass=10})f{bal=<BBAL>}}}<SEPA>;,"
829 "ole=<OLEA>,ose=<OSEP>},unc=n{sep=/(<TSTS>)?m{rat=0.7,"
830 "vert=100,low=h{pass=10},asc=b{width=3,bnd=f{bal=<BBAL>},"
831 "org=(|h{pass=10})f{bal=<BBAL>}}}<SEPA>;"
832 ",ole=<OLEA>,ose=<OSEP>}}";
833
834 // Replace flags in the string with appropriate values.
835 boost::replace_all(strat_str, "<SEPA>",
836 "|m{rat=0.7,vert=100,low=h{pass=10},"
837 "asc=b{width=3,bnd=f{bal=<BBAL>},"
838 "org=(|h{pass=10})f{bal=<BBAL>}}}");
839 boost::replace_all(strat_str, "<OSEP>", "s");
840 boost::replace_all(strat_str, "<OLEA>", "s");
841 boost::replace_all(strat_str, "<BBAL>", "0.1");
842 boost::replace_all(strat_str, "<TSTS>",
843 "vert>" + std::to_string(mdswitch));
844
845 // Set up the re-ordering strategy.
846 SCOTCH_Strat strat;
847 SCOTCH_CALL(SCOTCH_stratInit, (&strat));
848 SCOTCH_CALL(SCOTCH_stratGraphOrder, (&strat, strat_str.c_str()));
849
850 // As output, Scotch will give us the total number of 'blocks'
851 // (i.e. the separators and all of the leaves), the separator
852 // tree as a mapping of block to parent block, and the range of
853 // indices that is contained within each block. Reordering of
854 // the vertices goes from largest index (at the top level) to
855 // smallest (at the bottom level). The precise ordering is given
856 // in the Scotch user guide.
857 //
858 // Note that we pass in iperm into the 'permtab' field of
859 // graphOrder and 'perm' into the 'peritab' field; this is
860 // because our definition of the permutation is the opposite of
861 // what's defined in Scotch (this is leftover from a previous
862 // implementation that used Metis).
863 Array<OneD, int> treetab(nNonPartition);
864 Array<OneD, int> rangtab(nNonPartition + 1);
865 int cblknbr = 0;
866 SCOTCH_CALL(SCOTCH_graphOrder,
867 (scGraph, &strat, &iperm_tmp[0], &perm_tmp[0], &cblknbr,
868 &rangtab[0], &treetab[0]));
869
870 // We're now done with Scotch: clean up the created structures.
871 SCOTCH_graphExit(scGraph);
872 SCOTCH_stratExit(&strat);
873
874 //
875 // Step 3: create a MultiLevelBisectedGraph by reading the
876 // separator tree we obtained from Scotch.
877 //
878
879 // Setup root block, which lies at the end of the blocks
880 // described in treetab[].
881 std::vector<MultiLevelBisectedGraphSharedPtr> graphs(cblknbr);
882
883 // The strategy now is to traverse backwards over the blocks
884 // described in treetab to set up the levels of the top-down
885 // graph. rangtab allows us to calculate how many degrees of
886 // freedom lie in the separator.
887 for (i = cblknbr - 1; i >= 0; --i)
888 {
889 // Set up this block.
890 graphs[i] =
892 rangtab[i + 1] - rangtab[i]);
893
894 // If we're a root block (treetab[i] == -1) we don't need to
895 // do anything, just move onto the next block.
896 if (treetab[i] == -1)
897 {
898 continue;
899 }
900
901 // Now use treetab[i] to figure out the parent block. We
902 // have to be a bit careful in setting left/right daughters
903 // here. The left daughter's degrees of freedom are ordered
904 // _first_ in the iperm/perm arrays returned from Scotch,
905 // but if there is both a left and right daughter, we'll
906 // come across the right daughter first because the
907 // separators are being traversed backwards. We'll therefore
908 // insert this at the beginning of the daughter graphs
909 // vector.
910 MultiLevelBisectedGraphSharedPtr tmp = graphs[treetab[i]];
911 std::vector<MultiLevelBisectedGraphSharedPtr> &daughters =
912 tmp->GetDaughterGraphs();
913 daughters.insert(daughters.begin(), graphs[i]);
914 }
915
916 // Change permutations from Scotch to account for initial offset
917 // in case we had partition vertices.
918 for (i = 0; i < nGraphVerts; ++i)
919 {
920 if (partVerts.count(i) == 0)
921 {
922 iperm[i] = iperm_tmp[initial_perm[i]];
923 perm[iperm[i]] = i;
924 }
925 }
926
927 auto it = partVerts.begin(), it2 = partVerts.end();
928 for (i = nNonPartition; it != it2; ++it, ++i)
929 {
930 perm[i] = *it;
931 iperm[*it] = i;
932 }
933
934 for (i = 0; i < nGraphVerts; ++i)
935 {
936 ASSERTL1(perm[iperm[i]] == i, "Perm error " + std::to_string(i));
937 }
938
939 // If we were passed a graph with disconnected regions, we need
940 // to create a bisected graph with the appropriate roots.
941 std::vector<int> rootBlocks;
942 for (i = 0; i < cblknbr; ++i)
943 {
944 if (treetab[i] == -1)
945 {
946 rootBlocks.push_back(i);
947 }
948 }
949
951 if (rootBlocks.size() == 1)
952 {
953 root = graphs[rootBlocks[0]];
954 }
955 else
956 {
958
959 for (int i = 0; i < rootBlocks.size(); ++i)
960 {
961 root->GetDaughterGraphs().push_back(graphs[rootBlocks[i]]);
962 }
963 }
964
965 // Check that our degree of freedom count in the constructed
966 // graph is the same as the number of degrees of freedom that we
967 // were given in the function input.
968 ASSERTL0(root->GetTotDofs() == nNonPartition,
969 "Error in constructing Scotch graph for multi-level"
970 " static condensation.");
971
972 //
973 // Step 4: Set up the bottom-up graph from the top-down graph,
974 // and reorder the permutation from Scotch.
975 //
976 substructgraph =
978 root, nPartition, true);
979
980 // Important: we cannot simply use the ordering given by Scotch
981 // as it does not order the different blocks as we would like
982 // it. Therefore, we use following command to re-order them
983 // again in the context of the bottom-up substructuring. As a
984 // result, we will now obtain an ordering where the interior
985 // degrees of freedom of the first (=bottom) level will be
986 // ordered last (block by block ofcoarse). The interior degrees
987 // of freedom of the second level will be ordered second to
988 // last, etc ... As a result, the boundary degrees of freedom of
989 // the last level (i.e. the dofs that will have to solved
990 // non-recursively) will be ordered first (after the Dirichlet
991 // Dofs that is). (this way, we actually follow the same idea
992 // and convention in the standard (non-multi-level) static
993 // condensation approach).
994 substructgraph->UpdateBottomUpReordering(perm, iperm);
995 }
996 else
997 {
998 // This is the very special case of a graph without any
999 // connectivity i.e. a collection of vertices without any edges
1000 for (int i = 0; i < nGraphVerts; i++)
1001 {
1002 perm[i] = i;
1003 iperm[i] = i;
1004 }
1005 substructgraph =
1007 nGraphVerts);
1008 }
1009#endif
1010}
1011
1012void NoReordering(const BoostGraph &graph, Array<OneD, int> &perm,
1013 Array<OneD, int> &iperm)
1014{
1015 int nGraphVerts = boost::num_vertices(graph);
1016
1017 ASSERTL1(perm.size() >= nGraphVerts && iperm.size() >= nGraphVerts,
1018 "Non-matching dimensions");
1019
1020 for (int i = 0; i < nGraphVerts; i++)
1021 {
1022 perm[i] = i;
1023 iperm[i] = i;
1024 }
1025}
1026} // namespace Nektar::MultiRegions
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:208
#define NEKERROR(type, msg)
Assert Level 0 – Fundamental assert which is used whether in FULLDEBUG, DEBUG or OPT compilation mode...
Definition: ErrorUtil.hpp:202
#define ASSERTL1(condition, msg)
Assert Level 1 – Debugging which is used whether in FULLDEBUG or DEBUG compilation mode....
Definition: ErrorUtil.hpp:242
#define SCOTCH_CALL(scotchFunc, args)
#define sign(a, b)
return the sign(b)*a
Definition: Polylib.cpp:47
General purpose memory allocation routines with the ability to allocate from thread specific memory p...
static std::shared_ptr< DataType > AllocateSharedPtr(const Args &...args)
Allocate a shared pointer from the memory pool.
void GetNintDofsPerPatch(const int whichlevel, Array< OneD, unsigned int > &outarray) const
void ExpandGraphWithVertexWeights(const Array< OneD, const int > &wgts)
std::vector< SubGraphSharedPtr > GetInteriorBlocks() const
void SetBottomUpReordering(Array< OneD, int > &iperm) const
int GetInteriorOffset(const int whichlevel, const int patch=0) const
BottomUpSubStructuredGraph(MultiLevelBisectedGraphSharedPtr graph, int nPartition=0, bool globaloffset=false)
void UpdateBottomUpReordering(Array< OneD, int > &perm, Array< OneD, int > &iperm) const
BottomUpSubStructuredGraphSharedPtr m_daughterGraph
void MaskPatches(const int leveltomask, Array< OneD, NekDouble > &maskarray) const
int GetNpatchesWithInterior(const int whichlevel) const
MultiLevelBisectedGraph(MultiLevelBisectedGraphSharedPtr oldLevel, const int nPartition)
void CollectLeaves(std::vector< SubGraphSharedPtr > &leaves) const
std::vector< MultiLevelBisectedGraphSharedPtr > m_daughterGraphs
Array< OneD, unsigned int > m_bndPatch
void SetPatchMap(const int n, const int patchId, const int dofId, const unsigned int bndPatch, const NekDouble sign)
void SetNewLevelMap(Array< OneD, const unsigned int > numLocalBndCondPerPatch, Array< OneD, const unsigned int > numLocalIntCondPerPatch)
Array< OneD, NekDouble > m_sign
void NoReordering(const BoostGraph &graph, Array< OneD, int > &perm, Array< OneD, int > &iperm)
void CuthillMckeeReordering(const BoostGraph &graph, Array< OneD, int > &perm, Array< OneD, int > &iperm)
void MultiLevelBisectionReordering(const BoostGraph &graph, Array< OneD, int > &perm, Array< OneD, int > &iperm, BottomUpSubStructuredGraphSharedPtr &substructgraph, std::set< int > partVerts, int mdswitch)
std::shared_ptr< BottomUpSubStructuredGraph > BottomUpSubStructuredGraphSharedPtr
bool SubGraphWithoutVerts(const SubGraphSharedPtr g)
std::shared_ptr< MultiLevelBisectedGraph > MultiLevelBisectedGraphSharedPtr
std::shared_ptr< SubGraph > SubGraphSharedPtr
double NekDouble