libROM  v1.0
Data-driven physical simulation library
SampleMesh.cpp
1 
11 #include "SampleMesh.hpp"
12 
13 using std::endl;
14 using std::make_pair;
15 using std::ofstream;
16 using std::ifstream;
17 
18 namespace CAROM {
19 
20 #define FULL_DOF_STENCIL
21 
22 void FindStencilElements(const vector<int>& sample_dofs_gid,
23  set<int>& elements,
24  ParFiniteElementSpace& fespace)
25 {
26  for (int k = 0; k < fespace.GetParMesh()->GetNE(); k++)
27  {
28  Array<int> dofs;
29  fespace.GetElementVDofs(k, dofs);
30 
31  for (vector<int>::const_iterator it = sample_dofs_gid.begin();
32  it != sample_dofs_gid.end(); ++it)
33  {
34  for (int i = 0; i < dofs.Size(); i++)
35  {
36  const int dof_i = dofs[i] >= 0 ? dofs[i] : -1 - dofs[i];
37  int global_dof = fespace.GetGlobalTDofNumber(dof_i);
38  if (global_dof == *it)
39  {
40  elements.insert(k);
41  goto next;
42  }
43  }
44  }
45 next:
46  continue;
47  }
48 }
49 
50 void FindSampledMeshEntities(const int type, const vector<int>& sample_dofs_gid,
51  set<int>& entities, ParFiniteElementSpace& fespace)
52 {
53  int N = 0;
54  switch (type)
55  {
56  case 0:
57  N = fespace.GetParMesh()->GetNE();
58  break;
59  case 1:
60  N = fespace.GetParMesh()->GetNFaces();
61  break;
62  case 2:
63  N = fespace.GetParMesh()->GetNEdges();
64  break;
65  case 3:
66  N = fespace.GetParMesh()->GetNV();
67  break;
68  }
69 
70  for (int k = 0; k < N; k++)
71  {
72  Array<int> dofs;
73 
74  switch (type)
75  {
76  case 0:
77  fespace.GetElementInteriorVDofs(k, dofs);
78  break;
79  case 1:
80  fespace.GetFaceVDofs(k, dofs);
81  break;
82  case 2:
83  fespace.GetEdgeVDofs(k, dofs);
84  break;
85  case 3:
86  fespace.GetVertexVDofs(k, dofs);
87  break;
88  }
89 
90  for (vector<int>::const_iterator it = sample_dofs_gid.begin();
91  it != sample_dofs_gid.end(); ++it)
92  {
93  for (int i = 0; i < dofs.Size(); i++)
94  {
95  const int dof_i = dofs[i] >= 0 ? dofs[i] : -1 - dofs[i];
96  int global_dof = fespace.GetGlobalTDofNumber(dof_i);
97  if (global_dof == *it)
98  {
99  entities.insert(k);
100  goto next;
101  }
102  }
103  }
104 next:
105  continue;
106  }
107 
108 }
109 
110 void GetLocalSampleMeshElements(ParMesh& pmesh, ParFiniteElementSpace& fespace,
111  const vector<int>& sample_dofs,
112  const vector<int>& local_num_sample_dofs, set<int>& elems,
113  bool getSampledEntities, set<int>& intElems, set<int>& faces, set<int>& edges,
114  set<int>& vertices)
115 {
116  int myid;
117  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
118  const int num_procs = local_num_sample_dofs.size();
119 
120  // Construct the map of local true dofs to global true dofs.
121  map<int, int> ltdof2gtdof;
122  const int ndofs = fespace.GetVSize();
123  for (int i = 0; i < fespace.GetVSize(); ++i) {
124  int ltdof = fespace.GetLocalTDofNumber(i);
125 
126  if (ltdof != -1) {
127  int gtdof = fespace.GetGlobalTDofNumber(i);
128  ltdof2gtdof[ltdof] = gtdof;
129  }
130  }
131 
132  // Convert local sample dof indices into global sample dof indices.
133  int* offsets = new int [num_procs];
134  offsets[0] = 0;
135  for (int i = 1; i < num_procs; ++i) {
136  offsets[i] = offsets[i-1] + local_num_sample_dofs[i-1];
137  }
138 
139  vector<int> local_sample_dofs_gid;
140  int my_offset = offsets[myid];
141  for (int i = 0; i < local_num_sample_dofs[myid]; ++i) {
142  map<int, int>::const_iterator it = ltdof2gtdof.find(sample_dofs[my_offset+i]);
143  MFEM_VERIFY(it != ltdof2gtdof.end(), "");
144  local_sample_dofs_gid.push_back(ltdof2gtdof[sample_dofs[my_offset+i]]);
145  }
146 
147  int total_num_sample_dofs = static_cast<int>(sample_dofs.size());
148  vector<int> sample_dofs_gid(total_num_sample_dofs);
149  MPI_Allgatherv(&local_sample_dofs_gid[0], local_num_sample_dofs[myid],
150  MPI_INT, &sample_dofs_gid[0], &local_num_sample_dofs[0],
151  offsets, MPI_INT, MPI_COMM_WORLD);
152 
153  // Get the processor local element ids of all elements connected to any
154  // sample_dofs_gid and the total number of elements in the stencil mesh.
155  elems.clear();
156  FindStencilElements(sample_dofs_gid, elems, fespace);
157 
158  if (getSampledEntities)
159  {
160  FindSampledMeshEntities(0, sample_dofs_gid, intElems, fespace);
161  if (fespace.GetParMesh()->Dimension() == 3) FindSampledMeshEntities(1,
162  sample_dofs_gid, faces, fespace);
163  FindSampledMeshEntities(2, sample_dofs_gid, edges, fespace);
164  FindSampledMeshEntities(3, sample_dofs_gid, vertices, fespace);
165  }
166 
167  delete [] offsets;
168 }
169 
170 void SplitDofsIntoBlocks(const vector<int>& Ntrue, const vector<int>& dofs,
171  const vector<int>& local_num_dofs,
172  vector<vector<int>>& dofs_block, vector<vector<int>>& dofs_block_todofs,
173  vector<vector<int>>& local_num_dofs_block)
174 {
175  const int num_procs = local_num_dofs.size();
176  const int nspaces = Ntrue.size();
177 
178  MFEM_VERIFY(nspaces > 0 && nspaces == dofs_block.size()
179  && nspaces == dofs_block_todofs.size()
180  && nspaces == local_num_dofs_block.size(), "");
181 
182  vector<vector<vector<int>>> procDofs_block(nspaces);
183  vector<vector<int>> allNtrue(nspaces);
184 
185  for (int i=0; i<nspaces; ++i)
186  {
187  local_num_dofs_block[i].resize(num_procs);
188  local_num_dofs_block[i].assign(local_num_dofs_block[i].size(), 0);
189  procDofs_block[i].resize(num_procs);
190 
191  allNtrue[i].resize(num_procs);
192  MPI_Allgather(&Ntrue[i], 1, MPI_INT, allNtrue[i].data(), 1, MPI_INT,
193  MPI_COMM_WORLD);
194  }
195 
196  vector<int> spaceOS(nspaces);
197  spaceOS[0] = 0;
198 
199  int os = 0;
200  vector<int>::const_iterator it = dofs.begin();
201  for (int p=0; p<num_procs; ++p)
202  {
203  for (int i=1; i<nspaces; ++i)
204  {
205  spaceOS[i] = spaceOS[i-1] + allNtrue[i-1][p];
206  }
207 
208  for (int i=0; i<local_num_dofs[p]; ++i, ++it)
209  {
210  bool found = false;
211  for (int s=nspaces-1; s>=0; --s)
212  {
213  if (*it >= spaceOS[s])
214  {
215  dofs_block[s].push_back(*it - spaceOS[s]);
216  dofs_block_todofs[s].push_back(os + i);
217  local_num_dofs_block[s][p]++;
218  found = true;
219  break;
220  }
221  }
222  MFEM_VERIFY(found, "Space not found");
223  }
224 
225  os += local_num_dofs[p];
226  }
227 
228  MFEM_VERIFY(it == dofs.end(), "");
229 }
230 
231 void InsertElementDofs(ParFiniteElementSpace& fespace, const int elId,
232  const int offset, set<int>& element_dofs)
233 {
234  Array<int> dofs;
235  fespace.GetElementVDofs(elId, dofs);
236  for (int i = 0; i < dofs.Size(); ++i) {
237  const int dof_i = dofs[i] >= 0 ? dofs[i] : -1 - dofs[i];
238 #ifdef FULL_DOF_STENCIL
239  element_dofs.insert(offset + dof_i);
240 #else
241  int ltdof = fespace.GetLocalTDofNumber(dof_i);
242  if (ltdof != -1) {
243  element_dofs.insert(offset + ltdof);
244  }
245 #endif
246  }
247 }
248 
249 // dofs are full dofs
250 void AugmentDofListWithOwnedDofs(vector<int>& mixedDofs,
251  vector<ParFiniteElementSpace*> & fespace)
252 {
253  const int nspaces = fespace.size();
254  vector<int> spaceOS(nspaces);
255 
256  spaceOS[0] = 0;
257  for (int i=1; i<nspaces; ++i)
258  spaceOS[i] = spaceOS[i-1] + fespace[i-1]->GetVSize();
259 
260  vector<vector<int> > dofs(nspaces);
261  set<int> mixedDofSet;
262  for (auto i : mixedDofs)
263  {
264  mixedDofSet.insert(i);
265 
266  int space = 0;
267  for (int j=nspaces-1; j > 0; --j)
268  {
269  if (i >= spaceOS[j])
270  {
271  space = j;
272  break;
273  }
274  }
275 
276  dofs[space].push_back(i - spaceOS[space]);
277  }
278 
279  int nprocs = -1;
280  MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
281 
282  vector<int> cts(nprocs);
283  vector<int> offsets(nprocs);
284 
285  for (int s=0; s<nspaces; ++s) // loop over fespaces
286  {
287  const int ndofs = dofs[s].size();
288  MPI_Allgather(&ndofs, 1, MPI_INT, cts.data(), 1, MPI_INT, MPI_COMM_WORLD);
289 
290  offsets[0] = 0;
291  for (int i = 1; i < nprocs; ++i)
292  offsets[i] = offsets[i-1] + cts[i-1];
293 
294  vector<int> gdofs(ndofs);
295  for (int i = 0; i<ndofs; ++i)
296  {
297  gdofs[i] = fespace[s]->GetGlobalTDofNumber(dofs[s][i]);
298  }
299 
300  vector<int> gdofsGathered(offsets[nprocs-1] + cts[nprocs-1]);
301 
302  MPI_Allgatherv(gdofs.data(), ndofs, MPI_INT,
303  gdofsGathered.data(), cts.data(), offsets.data(), MPI_INT, MPI_COMM_WORLD);
304 
305  set<int> allgdofs;
306 
307  for (auto i : gdofsGathered)
308  allgdofs.insert(i);
309 
310  for (int i=0; i<fespace[s]->GetVSize(); ++i)
311  {
312  int ltdof = fespace[s]->GetLocalTDofNumber(i);
313  if (ltdof != -1)
314  {
315  const int g = fespace[s]->GetGlobalTDofNumber(i);
316  set<int>::iterator it = allgdofs.find(g);
317  if (it != allgdofs.end())
318  {
319  // Dof i should be included in mixedDofs. First check whether it is already included.
320  const int j = spaceOS[s] + i;
321  set<int>::iterator it = mixedDofSet.find(j);
322  if (it == mixedDofSet.end())
323  {
324  mixedDofs.push_back(j);
325  mixedDofSet.insert(j);
326  }
327  }
328  }
329  }
330  }
331 }
332 
333 void BuildSampleMesh(ParMesh& pmesh, vector<ParFiniteElementSpace*> & fespace,
334  const set<int>& elems, Mesh*& sample_mesh, vector<int>& stencil_dofs,
335  vector<int>& elemLocalIndices, vector<map<int, int> >& elemLocalIndicesInverse)
336 {
337  // Get the number of stencil elements coming from each processor and the
338  // total number of stencil elements.
339  int num_procs = -1;
340  MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
341 
342  const int d = pmesh.Dimension();
343 
344  // Must be first order, to get a bijection between vertices and DOF's.
345  H1_FECollection h1_coll(1, d);
346 
347  // This constructor effectively sets vertex (DOF) global indices.
348  ParFiniteElementSpace H1DummySpace(&pmesh, &h1_coll);
349 
350  vector<int> procNumElems(num_procs);
351 
352  const int local_num_elems = elems.size();
353  int* cts = new int [num_procs];
354  int* offsets = new int [num_procs];
355  MPI_Allgather(&local_num_elems, 1, MPI_INT, cts, 1, MPI_INT, MPI_COMM_WORLD);
356  int sample_mesh_num_elems = 0;
357  offsets[0] = 0;
358  for (int i = 0; i < num_procs; ++i) {
359  sample_mesh_num_elems += cts[i];
360  procNumElems[i] = cts[i];
361  cts[i] *= 2;
362  if (i > 0)
363  offsets[i] = offsets[i-1] + cts[i-1];
364  }
365 
366  const int nspaces = fespace.size();
367 
368  vector<int> spaceN(nspaces);
369  for (int i=0; i<nspaces; ++i)
370  {
371 #ifdef FULL_DOF_STENCIL
372  spaceN[i] = fespace[i]->GetVSize();
373 #else
374  spaceN[i] = fespace[i]->TrueVSize();
375 #endif
376  }
377 
378  // Each process generates for each of its stencil elements a list of global
379  // TDofs (which are equivalent to vertices), a list of element attributes,
380  // and a list of vertices (coords). These get communicated to P0. For each
381  // stencil mesh element, P0 goes through the list of vertices and constructs
382  // a map<int, int> mapping these global TDofs to local vertex ids. For each
383  // unique TDof it finds, it adds a vertex to the sample mesh. Then it adds
384  // each element, attribute, and connectivity.
385  vector<int> my_element_attr(
386  2*local_num_elems); // Stores element attribute and local index
387  set<int> element_dofs;
388  int attr_idx = 0;
389  int conn_idx = 0;
390  int coords_idx = 0;
391  Array<int> elVert;
392  pmesh.GetElementVertices(*elems.begin(), elVert);
393  int numElVert = elVert.Size(); // number of vertices per element
394  MFEM_VERIFY(numElVert > 0, "");
395 
396  // vertex global indices, for each element
397  vector<int> my_element_vgid(numElVert*local_num_elems);
398  vector<double> my_element_coords(d*numElVert*local_num_elems);
399 
400  for (set<int>::iterator it = elems.begin(); it != elems.end(); ++it) {
401  const int elId = *it;
402  int os = 0;
403  for (int i=0; i<nspaces; ++i)
404  {
405  InsertElementDofs(*fespace[i], elId, os, element_dofs);
406  os += spaceN[i];
407  }
408 
409  pmesh.GetElementVertices(elId, elVert);
410  MFEM_VERIFY(numElVert == elVert.Size(),
411  "Assuming a uniform element type in the mesh.");
412  // NOTE: to be very careful, it should be verified that this is the same across all processes.
413 
414  Array<int> dofs;
415  H1DummySpace.GetElementDofs(elId, dofs);
416  MFEM_VERIFY(numElVert == dofs.Size(),
417  "Assuming a bijection between vertices and H1 dummy space DOF's");
418 
419  for (int i = 0; i < numElVert; ++i) {
420  my_element_vgid[conn_idx++] = H1DummySpace.GetGlobalTDofNumber(dofs[i]);
421  double* coords = pmesh.GetVertex(elVert[i]);
422  for (int j=0; j<d; ++j)
423  my_element_coords[coords_idx++] = coords[j];
424  }
425 
426  my_element_attr[attr_idx++] = pmesh.GetAttribute(elId);
427  my_element_attr[attr_idx++] = elId;
428  }
429 
430  stencil_dofs.assign(element_dofs.begin(), element_dofs.end());
431 
432 #ifdef FULL_DOF_STENCIL
433  AugmentDofListWithOwnedDofs(stencil_dofs, fespace);
434 #endif
435 
436  MFEM_VERIFY(coords_idx == d*numElVert*local_num_elems, "");
437  MFEM_VERIFY(conn_idx == numElVert*local_num_elems, "");
438  MFEM_VERIFY(attr_idx == 2*local_num_elems, "");
439 
440  // Gather all the element attributes from all processors.
441 
442  vector<int> element_attr(2*sample_mesh_num_elems);
443  MPI_Allgatherv(&my_element_attr[0], 2*local_num_elems, MPI_INT,
444  &element_attr[0], cts, offsets, MPI_INT, MPI_COMM_WORLD);
445 
446  // Gather all the element connectivities from all processors.
447  offsets[0] = 0;
448  cts[0] = numElVert*cts[0]/2;
449  for (int i = 1; i < num_procs; ++i) {
450  cts[i] = numElVert*cts[i]/2;
451  offsets[i] = offsets[i-1] + cts[i-1];
452  }
453  vector<int> element_vgid(numElVert*sample_mesh_num_elems);
454  MPI_Allgatherv(&my_element_vgid[0], numElVert*local_num_elems, MPI_INT,
455  &element_vgid[0], cts, offsets, MPI_INT, MPI_COMM_WORLD);
456 
457  // Gather all the element coordinates from all processors.
458  offsets[0] = 0;
459  cts[0] = d*cts[0];
460  for (int i = 1; i < num_procs; ++i) {
461  cts[i] = d*cts[i];
462  offsets[i] = offsets[i-1] + cts[i-1];
463  }
464  vector<double> element_coords(d*numElVert*sample_mesh_num_elems);
465  MPI_Allgatherv(&my_element_coords[0], d*numElVert*local_num_elems, MPI_DOUBLE,
466  &element_coords[0], cts, offsets, MPI_DOUBLE, MPI_COMM_WORLD);
467  delete [] cts;
468  delete [] offsets;
469 
470  // element_vgid holds vertices as global ids. Vertices may be shared
471  // between elements so we don't know the number of unique vertices in the
472  // sample mesh. Find all the unique vertices and construct the map of
473  // global dof ids to local dof ids (vertices). Keep track of the number of
474  // unique vertices.
475  set<int> unique_gdofs;
476  map<int, int> unique_gdofs_first_appearance;
477  for (int i = 0; i < numElVert*sample_mesh_num_elems; ++i) {
478  int gdof = element_vgid[i];
479  if (unique_gdofs.insert(gdof).second) {
480  unique_gdofs_first_appearance.insert(make_pair(gdof, i));
481  }
482  }
483  int sample_mesh_num_verts = unique_gdofs.size();
484  map<int, int> unique_gdofs_2_vertex;
485  map<int, int> vertex_2_unique_gdofs;
486  int idx = 0;
487  for (set<int>::iterator it = unique_gdofs.begin();
488  it != unique_gdofs.end(); ++it) {
489  unique_gdofs_2_vertex.insert(make_pair(*it, idx));
490  vertex_2_unique_gdofs.insert(make_pair(idx, *it));
491  ++idx;
492  }
493 
494  // Now we have enough info to build the sample mesh.
495  printf("sample mesh has %d elements, %d vertices\n",
496  sample_mesh_num_elems, sample_mesh_num_verts);
497 
498  sample_mesh = new Mesh(d, sample_mesh_num_verts, sample_mesh_num_elems);
499 
500  // For each vertex that we found, add its coordinates to the stencil mesh.
501  for (int vert = 0; vert < sample_mesh_num_verts; ++vert) {
502  int unique_gdof = vertex_2_unique_gdofs[vert];
503  int first_conn_ref = unique_gdofs_first_appearance[unique_gdof];
504  int coord_loc = d*first_conn_ref;
505  sample_mesh->AddVertex(&element_coords[coord_loc]);
506  }
507 
508  // Now add each element and give it its attributes and connectivity.
509  const int elGeom = pmesh.GetElementBaseGeometry(0);
510  idx = 0;
511  elemLocalIndices.resize(sample_mesh_num_elems);
512  elemLocalIndicesInverse.resize(num_procs);
513  int ielem = 0;
514  for (int p=0; p<num_procs; ++p)
515  {
516  for (int i=0; i<procNumElems[p]; ++i, ++ielem)
517  {
518  Element* sel = sample_mesh->NewElement(elGeom);
519  sel->SetAttribute(element_attr[2*ielem]);
520 
521  elemLocalIndices[ielem] = element_attr[(2*ielem)+1];
522  elemLocalIndicesInverse[p][elemLocalIndices[ielem]] = ielem;
523 
524  Array<int> sv(numElVert);
525  for (int vert = 0; vert < numElVert; ++vert) {
526  sv[vert] = unique_gdofs_2_vertex[element_vgid[idx++]];
527  }
528  sel->SetVertices(sv);
529 
530  sample_mesh->AddElement(sel);
531  }
532  }
533 
534  MFEM_VERIFY(ielem == sample_mesh_num_elems, "");
535 
536  sample_mesh->FinalizeTopology();
537 }
538 
539 void GetLocalDofsToLocalElementMap(ParFiniteElementSpace& fespace,
540  const vector<int>& dofs,
541  const vector<int>& localNumDofs,
542  const set<int>& elems,
543  vector<int>& dofToElem,
544  vector<int>& dofToElemDof,
545  const bool useTDof)
546 {
547  int myid;
548  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
549 
550  int myoffset = 0;
551  for (int p=0; p<myid; ++p)
552  myoffset += localNumDofs[p];
553 
554  dofToElem.resize(localNumDofs[myid]);
555  dofToElemDof.resize(localNumDofs[myid]);
556 
557  Array<int> eldofs;
558 
559  for (int i=0; i<localNumDofs[myid]; ++i)
560  {
561  dofToElem[i] = -1;
562  for (set<int>::const_iterator it = elems.begin(); it != elems.end(); ++it)
563  {
564  const int elId = *it;
565  fespace.GetElementVDofs(elId, eldofs);
566 
567  for (int j=0; j<eldofs.Size(); ++j)
568  {
569  const int eldof_j = eldofs[j] >= 0 ? eldofs[j] : -1 - eldofs[j];
570  int ltdof = fespace.GetLocalTDofNumber(eldof_j);
571 #ifdef FULL_DOF_STENCIL
572  const int dof_j = useTDof ? ltdof : eldof_j;
573  if (dof_j == dofs[myoffset + i]) // dofs contains true DOF's.
574 #else
575  if (ltdof == dofs[myoffset + i]) // dofs contains true DOF's.
576 #endif
577  {
578  // Possibly overwrite another element index, which is fine
579  // since we just want any element index.
580  dofToElem[i] = elId;
581  dofToElemDof[i] = j;
582  }
583  }
584  }
585 
586 #ifndef FULL_DOF_STENCIL
587  const int dte = dofToElem[i];
588  MFEM_VERIFY(dofToElem[i] >= 0, "");
589 #endif
590  }
591 }
592 
593 void Set_s2sp(const int myid, const int num_procs, vector<int> const& spNtrue,
594  const int global_num_sample_dofs, const vector<int>& local_num_sample_dofs,
595  const vector<vector<int> >& local_num_sample_dofs_sub,
596  const vector<vector<int> >& localSampleDofsToElem_sub,
597  const vector<vector<int> >& localSampleDofsToElemDof_sub,
598  const vector<vector<int> >& sample_dofs_sub_to_sample_dofs,
599  const vector<map<int, int> >& elemLocalIndicesInverse,
600  vector<ParFiniteElementSpace*> & spfespace,
601  vector<int>& s2sp)
602 {
603  // Set s2sp, which is a map from the selected indices in sample_dofs for all processes to DOF's in sample_mesh.
604  // This is done by the following steps:
605  // 1. For each local sample DOF, determine its fespace and find an element to which it belongs and its local index with respect to the element DOF ordering.
606  // 2. Communicate this data (one process-local element index and an element DOF index) from each process to P0.
607  // 3. On P0, loop over each process's sample DOF's and get their process-local element index, determine the sample mesh element,
608  // and then get the sample mesh DOF from the sample mesh element DOF's. This goes into s2sp.
609  // Note that two sample DOF's could be mapped to the same DOF in sample_mesh, which is fine. This may happen if a DOF is shared by two processes.
610  // The DEIM implementation only sees vectors on each process, without knowledge that the application may identify two DOF's.
611  // Fortunately, DEIM will not select two or more indices on different processes that are equivalent as DOF's, because the corresponding rows
612  // of the basis matrix would then be identical, and then the corresponding entries of the residual used in DEIM would be identical.
613  // Since DEIM does not select the same index twice, it also will not select two indices that have the same row data.
614  // Thus s2sp will not map two indices selected by DEIM to the same DOF in sample_mesh, but it may map two original mesh DOF's that are in
615  // the sample_mesh stencil to the same sample_mesh DOF, which should not cause any problems.
616 
617  const int nspaces = spfespace.size();
618  MFEM_VERIFY(nspaces == spNtrue.size(), "");
619 
620  int mySampleDofOffset = 0;
621  vector<int> os;
622  os.assign(nspaces, 0);
623  for (int p=0; p<myid; ++p)
624  {
625  mySampleDofOffset += local_num_sample_dofs[p];
626  for (int i=0; i<nspaces; ++i)
627  os[i] += local_num_sample_dofs_sub[i][p];
628  }
629 
630  // Gather all the sample DOF to element and element DOF indices.
631  vector<int> mySampleToElement(2*local_num_sample_dofs[myid]);
632 
633  // Initialize with invalid values, to verify later that everything was set.
634  mySampleToElement.assign(mySampleToElement.size(), -1);
635 
636  for (int s=0; s<nspaces; ++s) // Loop over subspaces
637  {
638  for (int i=0; i<local_num_sample_dofs_sub[s][myid]; ++i)
639  {
640  const int sdi = sample_dofs_sub_to_sample_dofs[s][os[s] + i] -
641  mySampleDofOffset;
642  mySampleToElement[2*sdi] = localSampleDofsToElem_sub[s][i];
643  mySampleToElement[(2*sdi)+1] = localSampleDofsToElemDof_sub[s][i];
644  }
645  }
646 
647 #ifndef FULL_DOF_STENCIL
648  for (int i=0; i<mySampleToElement.size(); ++i)
649  {
650  MFEM_VERIFY(mySampleToElement[i] >= 0, "");
651  }
652 #endif
653 
654  int* cts = new int [num_procs];
655  int* offsets = new int [num_procs];
656 
657  offsets[0] = 0;
658  cts[0] = 2*local_num_sample_dofs[0];
659  for (int i = 1; i < num_procs; ++i) {
660  cts[i] = 2*local_num_sample_dofs[i];
661  offsets[i] = offsets[i-1] + cts[i-1];
662  }
663 
664  // TODO: replace Allgatherv with just a gather to root?
665 
666  vector<int> sampleToElement(2*global_num_sample_dofs);
667  MPI_Allgatherv(&mySampleToElement[0], 2*local_num_sample_dofs[myid], MPI_INT,
668  &sampleToElement[0], cts, offsets, MPI_INT, MPI_COMM_WORLD);
669 
670  delete [] cts;
671  delete [] offsets;
672 
673  // The remaining code in this function only sets s2sp, only on the root process.
674  if (myid != 0)
675  return;
676 
677  vector<int> spaceOS(nspaces);
678 
679  spaceOS[0] = 0;
680  for (int i=1; i<nspaces; ++i)
681  spaceOS[i] = spaceOS[i-1] + spfespace[i-1]->GetVSize();
682 
683  s2sp.resize(global_num_sample_dofs);
684  // Initialize with invalid values, to verify later that everything was set.
685  s2sp.assign(s2sp.size(), -1);
686 
687  for (int s=0; s<nspaces; ++s)
688  os[s] = 0;
689 
690  int soffset = 0;
691 
692  for (int p=0; p<num_procs; ++p)
693  {
694  for (int s=0; s<nspaces; ++s) // Loop over subspaces
695  {
696  for (int i=0; i<local_num_sample_dofs_sub[s][p]; ++i)
697  {
698  const int sdi = sample_dofs_sub_to_sample_dofs[s][os[s] + i];
699  const int procElementIndex = sampleToElement[2*sdi];
700 #ifdef FULL_DOF_STENCIL
701  if (procElementIndex == -1)
702  continue;
703 #endif
704  const int procElementDofIndex = sampleToElement[(2*sdi)+1];
705  map<int, int>::const_iterator it = elemLocalIndicesInverse[p].find(
706  procElementIndex);
707 
708  MFEM_VERIFY(it != elemLocalIndicesInverse[p].end(), "");
709  MFEM_VERIFY(it->first == procElementIndex, "");
710 
711  const int sampleMeshElement = it->second;
712  Array<int> eldofs;
713  spfespace[s]->GetElementVDofs(sampleMeshElement, eldofs);
714  const int eldof = eldofs[procElementDofIndex] >= 0 ?
715  eldofs[procElementDofIndex] : -1 - eldofs[procElementDofIndex];
716  s2sp[sdi] = eldof + spaceOS[s];
717  }
718 
719  os[s] += local_num_sample_dofs_sub[s][p];
720  }
721 
722  soffset += local_num_sample_dofs[p];
723  }
724 
725 #ifdef FULL_DOF_STENCIL
726 
727 #else
728  for (int i=0; i<s2sp.size(); ++i)
729  {
730  MFEM_VERIFY(s2sp[i] >= 0, "");
731  }
732 #endif
733 }
734 
735 #ifdef FULL_DOF_STENCIL
736 void Finish_s2sp_augmented(const int rank, const int nprocs,
737  vector<ParFiniteElementSpace*> & fespace,
738  vector<vector<int>>& dofs_block, vector<vector<int> >& dofs_sub_to_sdofs,
739  vector<vector<int> >& local_num_dofs_sub, const bool dofsTrue,
740  vector<int> & s2sp_)
741 {
742  const int nspaces = fespace.size();
743 
744  vector<int> s2sp;
745  {
746  int n = rank == 0 ? s2sp_.size() : 0;
747  MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
748  s2sp.resize(n);
749  if (rank == 0)
750  s2sp = s2sp_;
751 
752  MPI_Bcast(s2sp.data(), n, MPI_INT, 0, MPI_COMM_WORLD);
753  }
754 
755  for (int s=0; s<nspaces; ++s) // loop over spaces
756  {
757  int os = 0;
758  for (int p=0; p<rank; ++p)
759  os += local_num_dofs_sub[s][p];
760 
761  {
762  int sum = 0;
763  for (int p=0; p<nprocs; ++p)
764  sum += local_num_dofs_sub[s][p];
765  MFEM_VERIFY(dofs_sub_to_sdofs[s].size() == sum
766  && dofs_block[s].size() == sum, "");
767  }
768 
769  const int ndof = local_num_dofs_sub[s][rank];
770  MFEM_VERIFY(os + ndof <= dofs_block[s].size(), "");
771 
772  vector<int> gid(2*ndof);
773 
774  if (dofsTrue)
775  {
776  vector<int> fdofs;
777 
778  map<int, int> tdofIndex;
779  for (int i=0; i<ndof; ++i)
780  {
781  tdofIndex[dofs_block[s][os + i]] = i;
782  }
783 
784  fdofs.assign(ndof, -1);
785  for (int i=0; i<fespace[s]->GetVSize(); ++i)
786  {
787  const int ltdof = fespace[s]->GetLocalTDofNumber(i);
788  map<int, int>::const_iterator it = tdofIndex.find(ltdof);
789  if (it != tdofIndex.end())
790  {
791  MFEM_VERIFY(it->first == ltdof && fdofs[it->second] == -1, "");
792  fdofs[it->second] = i;
793  }
794  }
795 
796  bool fdofsSet = true;
797  for (int i=0; i<ndof; ++i)
798  {
799  if (fdofs[i] < 0)
800  fdofsSet = false;
801  }
802 
803  MFEM_VERIFY(fdofsSet, "");
804  for (int i=0; i<ndof; ++i)
805  {
806  gid[2*i] = fespace[s]->GetGlobalTDofNumber(fdofs[i]);
807  }
808  }
809  else
810  {
811  for (int i=0; i<ndof; ++i)
812  {
813  gid[2*i] = fespace[s]->GetGlobalTDofNumber(dofs_block[s][os + i]);
814  }
815  }
816 
817  for (int i=0; i<ndof; ++i)
818  {
819  gid[(2*i) + 1] = s2sp[dofs_sub_to_sdofs[s][os + i]];
820  }
821 
822  vector<int> counts(nprocs);
823  vector<int> offsets(nprocs);
824 
825  offsets[0] = 0;
826  for (int i=0; i<nprocs; ++i)
827  {
828  counts[i] = 2 * local_num_dofs_sub[s][i];
829  if (i > 0)
830  offsets[i] = offsets[i-1] + counts[i-1];
831  }
832 
833  vector<int> allgid(offsets[nprocs-1] + counts[nprocs-1]);
834 
835  MPI_Gatherv(gid.data(), 2*ndof, MPI_INT, allgid.data(), counts.data(),
836  offsets.data(), MPI_INT, 0, MPI_COMM_WORLD);
837 
838  if (rank == 0)
839  {
840  map<int, int> gi2sp;
841  // Set gi2sp
842  for (int p=0; p<nprocs; ++p)
843  {
844  for (int i=0; i<local_num_dofs_sub[s][p]; ++i)
845  {
846  const int g = allgid[offsets[p] + (2*i)];
847  const int sp = allgid[offsets[p] + (2*i) + 1];
848 
849  map<int, int>::const_iterator it = gi2sp.find(g);
850  if (it == gi2sp.end())
851  gi2sp[g] = sp;
852  else
853  {
854  MFEM_VERIFY(it->first == g, "");
855  if (it->second == -1)
856  gi2sp[g] = sp;
857  else
858  {
859  MFEM_VERIFY(it->second == sp, "");
860  }
861  }
862  }
863  }
864 
865  os = 0;
866  for (int p=0; p<nprocs; ++p)
867  {
868  for (int i=0; i<local_num_dofs_sub[s][p]; ++i)
869  {
870  const int g = allgid[offsets[p] + (2*i)];
871 
872  map<int, int>::const_iterator it = gi2sp.find(g);
873  MFEM_VERIFY(it != gi2sp.end() && it->first == g, "");
874 
875  if (s2sp[dofs_sub_to_sdofs[s][os + i]] != -1)
876  {
877  MFEM_VERIFY(s2sp[dofs_sub_to_sdofs[s][os + i]] == it->second, "");
878  }
879 
880  s2sp[dofs_sub_to_sdofs[s][os + i]] = it->second;
881  }
882 
883  os += local_num_dofs_sub[s][p];
884  }
885 
886  for (int i=0; i<s2sp_.size(); ++i)
887  {
888  if (s2sp_[i] == -1)
889  s2sp_[i] = s2sp[i];
890  else
891  MFEM_VERIFY(s2sp_[i] == s2sp[i], "Consistency check");
892  }
893  }
894  }
895 
896  for (int i=0; i<s2sp_.size(); ++i)
897  MFEM_VERIFY(s2sp_[i] >= 0 && s2sp_[i] == s2sp[i], "");
898 }
899 #endif
900 
901 void ParaViewPrintAttributes(const string &fname,
902  Mesh &mesh,
903  int entity_dim,
904  const Array<int> *el_number=nullptr,
905  const Array<int> *vert_number=nullptr)
906 {
907  ofstream out(fname + ".vtu");
908 
909  out << "<VTKFile type=\"UnstructuredGrid\" version=\"0.1\"";
910  out << " byte_order=\"" << VTKByteOrder() << "\">\n";
911  out << "<UnstructuredGrid>\n";
912 
913  const string fmt_str = "ascii";
914 
915  int dim = mesh.Dimension();
916  int ne = 0;
917  if (entity_dim == 1)
918  {
919  if (dim == 1) {
920  ne = mesh.GetNE();
921  }
922  else {
923  ne = mesh.GetNEdges();
924  }
925  }
926  else if (entity_dim == 2)
927  {
928  if (dim == 2) {
929  ne = mesh.GetNE();
930  }
931  else {
932  ne = mesh.GetNFaces();
933  }
934  }
935  else if (entity_dim == 3)
936  {
937  ne = mesh.GetNE();
938  }
939  int np = mesh.GetNV();
940 
941  auto get_geom = [mesh,entity_dim,dim](int i)
942  {
943  if (entity_dim == 1) {
944  return Geometry::SEGMENT;
945  }
946  else if (entity_dim == 2 && dim > 2) {
947  return mesh.GetFaceGeometry(i);
948  }
949  else {
950  return mesh.GetElementGeometry(i);
951  }
952  };
953 
954  auto get_verts = [mesh,entity_dim,dim](int i, Array<int> &v)
955  {
956  if (entity_dim == dim) {
957  mesh.GetElementVertices(i, v);
958  }
959  else if (entity_dim == 1) {
960  mesh.GetEdgeVertices(i, v);
961  }
962  else if (entity_dim == 2) {
963  mesh.GetFaceVertices(i, v);
964  }
965  };
966 
967  out << "<Piece NumberOfPoints=\"" << np << "\" NumberOfCells=\""
968  << ne << "\">\n";
969 
970  // print out the points
971  out << "<Points>\n";
972  out << "<DataArray type=\"" << "Float64"
973  << "\" NumberOfComponents=\"3\" format=\"" << fmt_str << "\">\n";
974  for (int i = 0; i < np; i++)
975  {
976  const double *v = mesh.GetVertex(i);
977  for (int d = 0; d < 3; ++ d)
978  {
979  if (d < mesh.SpaceDimension()) {
980  out << v[d] << " ";
981  }
982  else {
983  out << "0.0 ";
984  }
985  }
986  out << '\n';
987  }
988  out << "</DataArray>" << endl;
989  out << "</Points>" << endl;
990 
991  out << "<Cells>" << endl;
992  out << "<DataArray type=\"Int32\" Name=\"connectivity\" format=\""
993  << fmt_str << "\">" << endl;
994  for (int i = 0; i < ne; i++)
995  {
996  Array<int> v;
997  Geometry::Type geom = get_geom(i);
998  get_verts(i, v);
999  const int *p = VTKGeometry::VertexPermutation[geom];
1000  for (int j = 0; j < v.Size(); ++j)
1001  {
1002  out << v[p ? p[j] : j] << " ";
1003  }
1004  out << '\n';
1005  }
1006  out << "</DataArray>" << endl;
1007 
1008  out << "<DataArray type=\"Int32\" Name=\"offsets\" format=\""
1009  << fmt_str << "\">" << endl;
1010  // offsets
1011  int coff = 0;
1012  for (int i = 0; i < ne; ++i)
1013  {
1014  Array<int> v;
1015  get_verts(i, v);
1016  coff += v.Size();
1017  out << coff << '\n';
1018  }
1019  out << "</DataArray>" << endl;
1020  out << "<DataArray type=\"UInt8\" Name=\"types\" format=\""
1021  << fmt_str << "\">" << endl;
1022  // cell types
1023  for (int i = 0; i < ne; i++)
1024  {
1025  Geometry::Type geom = get_geom(i);
1026  out << VTKGeometry::Map[geom] << '\n';
1027  }
1028  out << "</DataArray>" << endl;
1029  out << "</Cells>" << endl;
1030 
1031  out << "<CellData Scalars=\"attribute\">" << endl;
1032 
1033  if (el_number)
1034  {
1035  string array_name;
1036  if (entity_dim == dim) {
1037  array_name = "element number";
1038  }
1039  else if (entity_dim == 2) {
1040  array_name = "face number";
1041  }
1042  else if (entity_dim == 1) {
1043  array_name = "edge number";
1044  }
1045  out << "<DataArray type=\"Int32\" Name=\""
1046  << array_name << "\" format=\""
1047  << fmt_str << "\">" << endl;
1048  for (int i = 0; i < ne; i++)
1049  {
1050  out << (*el_number)[i] << '\n';
1051  }
1052  out << "</DataArray>" << endl;
1053  }
1054  out << "</CellData>" << endl;
1055 
1056  if (vert_number)
1057  {
1058  out << "<PointData>" << endl;
1059  out << "<DataArray type=\"Int32\" Name=\"vertex number\" format=\""
1060  << fmt_str << "\">" << endl;
1061  for (int i = 0; i < np; i++)
1062  {
1063  out << (*vert_number)[i] << '\n';
1064  }
1065  out << "</DataArray>" << endl;
1066  out << "</PointData>" << endl;
1067  }
1068 
1069  out << "</Piece>\n"; // need to close the piece open in the PrintVTU method
1070  out << "</UnstructuredGrid>\n";
1071  out << "</VTKFile>" << endl;
1072 }
1073 
1074 SampleMeshManager::SampleMeshManager(vector<ParFiniteElementSpace*> & fespace_,
1075  string visFileName, double visScale) :
1076  nspaces(fespace_.size()), fespace(fespace_), filename(visFileName),
1077  elemVisScale(visScale)
1078 {
1079  MFEM_VERIFY(nspaces > 0, "Must provide at least one finite element space");
1080  spfespace.assign(nspaces, nullptr);
1081 
1082  pmesh = fespace[0]->GetParMesh();
1083 
1084  for (int i=1; i<nspaces; ++i)
1085  {
1086  MFEM_VERIFY(pmesh == fespace[i]->GetParMesh(),
1087  "All spaces must use the same full-order mesh");
1088  }
1089 
1090  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
1091  MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
1092 
1093  int color = (myid != 0);
1094  const int status = MPI_Comm_split(MPI_COMM_WORLD, color, myid, &root_comm);
1095  MFEM_VERIFY(status == MPI_SUCCESS,
1096  "Construction of hyperreduction comm failed");
1097 
1098  sample_dofs_proc.assign(nspaces, vector<set<int>> (nprocs));
1099 
1100  finalized = false;
1101 }
1102 
1104  const int space, vector<int> const& sample_dofs_v,
1105  vector<int> const& num_sample_dofs_per_proc)
1106 {
1107  MFEM_VERIFY(!finalized,
1108  "Cannot register a variable in a finalized SampleMeshManager");
1109  MFEM_VERIFY(0 <= space && space < nspaces, "Invalid space index");
1110  MFEM_VERIFY(num_sample_dofs_per_proc.size() == nprocs, "");
1111 
1112  {
1113  auto search = vmap.find(variable);
1114  MFEM_VERIFY(search == vmap.end(),
1115  "Variable " + variable + " is already registered!");
1116  }
1117  vmap[variable] = nvar;
1118 
1119  for (int p=0; p<nprocs; ++p)
1120  {
1121  int os = 0;
1122  for (int q=0; q<p; ++q)
1123  {
1124  os += num_sample_dofs_per_proc[q];
1125  }
1126 
1127  if (p == nprocs-1)
1128  {
1129  MFEM_VERIFY(os + num_sample_dofs_per_proc[p] == sample_dofs_v.size(), "");
1130  }
1131 
1132  for (int j=0; j<num_sample_dofs_per_proc[p]; ++j)
1133  sample_dofs_proc[space][p].insert(sample_dofs_v[os + j]);
1134  }
1135 
1136  num_sample_dofs_per_proc_var.push_back(num_sample_dofs_per_proc);
1137  sample_dofs_var.push_back(sample_dofs_v);
1138  varSpace.push_back(space);
1139  nvar = varSpace.size();
1140 }
1141 
1142 void SampleMeshManager::SetSampleMaps()
1143 {
1144  num_sample_dofs_per_proc_merged.assign(nprocs, 0);
1145 
1146  vector<vector<int>> allspaceTOS(nspaces);
1147 
1148  for (int i=0; i<nspaces; ++i)
1149  {
1150  allspaceTOS[i].resize(nprocs);
1151  MPI_Allgather(&spaceTOS[i], 1, MPI_INT, allspaceTOS[i].data(), 1, MPI_INT,
1152  MPI_COMM_WORLD);
1153  }
1154 
1155  for (int p=0; p<nprocs; ++p)
1156  {
1157  vector<int> sample_dofs_os(nspaces);
1158 
1159  for (int i=0; i<nspaces; ++i)
1160  {
1161  num_sample_dofs_per_proc_merged[p] += sample_dofs_proc[i][p].size();
1162  const int os = allspaceTOS[i][p];
1163  sample_dofs_os[i] = sample_dofs.size();
1164 
1165  for (set<int>::const_iterator it = sample_dofs_proc[i][p].begin();
1166  it != sample_dofs_proc[i][p].end(); ++it)
1167  {
1168  sample_dofs.push_back(os + (*it));
1169  }
1170  }
1171 
1172  // For each variable v and each of the num_sample_dofs_per_proc[v][p]
1173  // samples, set s2sp_var[v][] to be its index in sample_dofs.
1174  s2sp_var.resize(nvar);
1175  for (int v=0; v<nvar; ++v)
1176  {
1177  const int space = varSpace[v];
1178 
1179  int os = 0;
1180  for (int q=0; q<p; ++q)
1181  os += num_sample_dofs_per_proc_var[v][q];
1182 
1183  const int nvs = sample_dofs_var[v].size();
1184  s2sp_var[v].resize(nvs);
1185 
1186  for (int j=0; j<num_sample_dofs_per_proc_var[v][p]; ++j)
1187  {
1188  const int sample = sample_dofs_var[v][os + j];
1189 
1190  // Note: this has quadratic complexity and could be improved
1191  // with a std::map<int, int>, but it should not be a bottleneck.
1192  int k = -1;
1193  int cnt = 0;
1194  for (set<int>::const_iterator it = sample_dofs_proc[space][p].begin();
1195  it != sample_dofs_proc[space][p].end(); ++it, ++cnt)
1196  {
1197  if (*it == sample)
1198  {
1199  MFEM_VERIFY(k == -1, "");
1200  k = cnt;
1201  }
1202  }
1203 
1204  MFEM_VERIFY(k >= 0, "");
1205  s2sp_var[v][os + j] = sample_dofs_os[space] + k;
1206  }
1207  }
1208  } // loop over p
1209 }
1210 
1212 {
1213  MFEM_VERIFY(!finalized, "Sample mesh is already constructed");
1214 
1215  nvar = varSpace.size();
1216  MFEM_VERIFY(nvar == sample_dofs_var.size()
1217  && nvar == num_sample_dofs_per_proc_var.size(), "");
1218 
1219  spaceOS.assign(nspaces + 1, 0);
1220  spaceTOS.assign(nspaces + 1, 0);
1221  spaceOSSP.assign(nspaces, 0);
1222 
1223  for (int i=0; i<nspaces; ++i)
1224  {
1225  spaceOS[i+1] = spaceOS[i] + fespace[i]->GetVSize();
1226  spaceTOS[i+1] = spaceTOS[i] + fespace[i]->GetTrueVSize();
1227  }
1228 
1229  SetSampleMaps();
1230  CreateSampleMesh();
1231 
1232  if (myid == 0)
1233  {
1234  for (int i = 0; i < nspaces - 1; ++i)
1235  {
1236  spaceOSSP[i+1] = spaceOSSP[i] + spfespace[i]->GetVSize();
1237  }
1238 
1239  sample_pmesh->EnsureNodes();
1240  }
1241 
1242  FinishSampleMaps();
1243 
1244  finalized = true;
1245 }
1246 
1247 void SampleMeshManager::FinishSampleMaps()
1248 {
1249  {
1250  // TODO: if this is used in more than one place, then store it in the class.
1251  // Set spaceOSall by gathering spaceTOS from all processes, for all spaces.
1252  vector<int> data(nprocs * (nspaces+1));
1253  MPI_Allgather(spaceTOS.data(), nspaces+1, MPI_INT, data.data(), nspaces+1,
1254  MPI_INT, MPI_COMM_WORLD);
1255 
1256  spaceOSall.assign(nspaces+1, vector<int> (nprocs, 0));
1257  for (int p=0; p<nprocs; ++p)
1258  for (int s=0; s<nspaces+1; ++s)
1259  spaceOSall[s][p] = data[(p*(nspaces+1)) + s];
1260  }
1261 
1262  if (myid != 0)
1263  return;
1264 
1265  int offset = 0;
1266  for (int p=0; p<nprocs; ++p)
1267  {
1268  for (int i=0; i<num_sample_dofs_per_proc_merged[p]; ++i)
1269  {
1270  // Determine space index s based on offsets
1271  int s = nspaces - 1;
1272  while (sample_dofs[offset + i] < spaceOSall[s][p])
1273  s--;
1274  }
1275 
1276  offset += num_sample_dofs_per_proc_merged[p];
1277  }
1278 
1279  MFEM_VERIFY(s2sp.size() == offset, "");
1280 
1281  // Define the map s2sp_var from variable samples to sample mesh dofs.
1282  for (int v=0; v<nvar; ++v)
1283  {
1284  const int s = varSpace[v];
1285  int os_p = 0;
1286  for (int p=0; p<nprocs; ++p)
1287  {
1288  for (int j=0; j<num_sample_dofs_per_proc_var[v][p]; ++j)
1289  {
1290  MFEM_VERIFY(spaceOSall[s][p] <= sample_dofs[s2sp_var[v][os_p + j]]
1291  && sample_dofs[s2sp_var[v][os_p + j]] < spaceOSall[s+1][p], "");
1292  const int spId = s2sp[s2sp_var[v][os_p + j]];
1293  s2sp_var[v][os_p + j] = spId - spaceOSSP[s];
1294  }
1295 
1296  os_p += num_sample_dofs_per_proc_var[v][p];
1297  }
1298 
1299  MFEM_VERIFY(os_p == sample_dofs_var[v].size(), "");
1300  }
1301 }
1302 
1303 int SampleMeshManager::GetNumVarSamples(const string variable) const
1304 {
1305  const int var = GetVariableIndex(variable);
1306  return sample_dofs_var[var].size();
1307 }
1308 
1309 void SampleMeshManager::GetSampledValues(const string variable,
1310  mfem::Vector const& v, CAROM::Vector & s) const
1311 {
1312  const int var = GetVariableIndex(variable);
1313  const int n = s2sp_var[var].size();
1314  const int space = varSpace[var];
1315  MFEM_VERIFY(s.dim() == n, "");
1316  MFEM_VERIFY(v.Size() == spfespace[space]->GetTrueVSize(), "");
1317  for (int i=0; i<n; ++i)
1318  s(i) = v[s2sp_var[var][i]];
1319 }
1320 
1322  string file_name) const
1323 {
1324  const int var = GetVariableIndex(variable);
1325  ofstream file;
1326  file.open(file_name);
1327  for (int i=0; i<s2sp_var[var].size(); ++i)
1328  {
1329  file << s2sp_var[var][i] << endl;
1330  }
1331  file.close();
1332 }
1333 
1334 void GatherDistributedMatrixRows_aux(const CAROM::Matrix& B, const int rdim,
1335 #ifdef FULL_DOF_STENCIL
1336  const int os0, const int os1, const int ossp,
1337  ParFiniteElementSpace& fespace,
1338 #endif
1339  const vector<int>& st2sp, const vector<int>& sprows,
1340  const vector<int>& all_sprows, CAROM::Matrix& Bsp)
1341 {
1342  // Create B sample+ matrix Bsp (B^s+)
1343  // On P0 get number of rows each processor contributes to Bsp.
1344 
1345  int num_procs, myid;
1346  MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
1347  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
1348  MFEM_VERIFY(rdim <= B.numColumns(), "");
1349 
1350  vector<int> allos0(num_procs);
1351  vector<int> allos1(num_procs);
1352 
1353  MPI_Allgather(&os0, 1, MPI_INT, allos0.data(), 1, MPI_INT, MPI_COMM_WORLD);
1354  MPI_Allgather(&os1, 1, MPI_INT, allos1.data(), 1, MPI_INT, MPI_COMM_WORLD);
1355 
1356  const int num_sprows = static_cast<int>(sprows.size());
1357 
1358 #ifdef FULL_DOF_STENCIL
1359  int num_sprows_true = 0;
1360  for (auto i : sprows)
1361  {
1362  if (i >= os0 && i < os1 && fespace.GetLocalTDofNumber(i - os0) >= 0)
1363  {
1364  num_sprows_true++;
1365  }
1366  }
1367 
1368  vector<int> sprows_true(num_sprows_true);
1369  {
1370  int tcnt = 0;
1371  for (int j=0; j<sprows.size(); ++j)
1372  {
1373  const int i = sprows[j];
1374  if (i >= os0 && i < os1 && fespace.GetLocalTDofNumber(i - os0) >= 0)
1375  {
1376  sprows_true[tcnt] = j;
1377  tcnt++;
1378  }
1379  }
1380 
1381  MFEM_VERIFY(tcnt == num_sprows_true, "");
1382  }
1383 #endif
1384 
1385  int* cts = new int [num_procs];
1386 #ifdef FULL_DOF_STENCIL
1387  MPI_Allgather(&num_sprows_true, 1, MPI_INT, cts, 1, MPI_INT, MPI_COMM_WORLD);
1388  int* allNSP = new int [num_procs];
1389  MPI_Allgather(&num_sprows, 1, MPI_INT, allNSP, 1, MPI_INT, MPI_COMM_WORLD);
1390  int* offsetSP = new int [num_procs];
1391  offsetSP[0] = 0;
1392  for (int i = 1; i < num_procs; ++i) {
1393  offsetSP[i] = offsetSP[i-1] + allNSP[i-1];
1394  }
1395 #else
1396  MPI_Allgather(&num_sprows, 1, MPI_INT, cts, 1, MPI_INT, MPI_COMM_WORLD);
1397 #endif
1398  int* offsets = new int [num_procs];
1399  offsets[0] = 0;
1400  for (int i = 1; i < num_procs; ++i) {
1401  offsets[i] = offsets[i-1] + cts[i-1];
1402  }
1403 
1404 #ifdef FULL_DOF_STENCIL
1405  const int total_num_sprows_true = offsets[num_procs-1] + cts[num_procs-1];
1406  vector<int> all_sprows_true(total_num_sprows_true);
1407 
1408  MPI_Allgatherv(sprows_true.data(), num_sprows_true,
1409  MPI_INT, all_sprows_true.data(), cts,
1410  offsets, MPI_INT, MPI_COMM_WORLD);
1411 #else
1412  MFEM_VERIFY(offsets[num_procs-1] + cts[num_procs-1] == all_sprows.size(), "");
1413 #endif
1414 
1415  if (myid == 0) {
1416  const int Nsp = Bsp.numRows();
1417 
1418  for (int i = 0; i < num_sprows; i++) {
1419  int row = sprows[i];
1420  if (os0 <= row && row < os1)
1421  {
1422 #ifdef FULL_DOF_STENCIL
1423  const int ltdof = fespace.GetLocalTDofNumber(row - os0);
1424  if (ltdof >= 0 && st2sp[i] >= 0)
1425  {
1426  MFEM_VERIFY(0 <= st2sp[i] - ossp && st2sp[i] - ossp < Nsp, "");
1427 
1428  for (int j = 0; j < rdim; ++j)
1429  Bsp(st2sp[i] - ossp, j) = B(ltdof, j);
1430  }
1431 #else
1432  for (int j = 0; j < rdim; ++j)
1433  Bsp(st2sp[i], j) = B(row, j);
1434 #endif
1435  }
1436  }
1437 
1438 #ifdef FULL_DOF_STENCIL
1439  int Bsp_row = num_sprows_true;
1440 #else
1441  int Bsp_row = num_sprows;
1442 #endif
1443  MPI_Status status;
1444  for (int i = 1; i < num_procs; ++i) {
1445  for (int j = 0; j < cts[i]; ++j) {
1446 #ifdef FULL_DOF_STENCIL
1447  const int sti = offsetSP[i] + all_sprows_true[Bsp_row];
1448  if (allos0[i] <= all_sprows[sti] && all_sprows[sti] < allos1[i])
1449  {
1450  MFEM_VERIFY(0 <= st2sp[sti] - ossp && st2sp[sti] - ossp < Bsp.numRows(), "");
1451  // Note that this may redundantly overwrite some rows corresponding to shared DOF's.
1452  MPI_Recv(&Bsp(st2sp[sti] - ossp, 0), rdim, MPI_DOUBLE,
1453  i, offsets[i]+j, MPI_COMM_WORLD, &status);
1454  }
1455 #else
1456  if (allos0[i] <= all_sprows[Bsp_row] && all_sprows[Bsp_row] < allos1[i])
1457  {
1458  // Note that this may redundantly overwrite some rows corresponding to shared DOF's.
1459  MPI_Recv(&Bsp(st2sp[Bsp_row], 0), rdim, MPI_DOUBLE,
1460  i, offsets[i]+j, MPI_COMM_WORLD, &status);
1461  }
1462 #endif
1463  ++Bsp_row;
1464  }
1465  }
1466 
1467 #ifdef FULL_DOF_STENCIL
1468  MFEM_VERIFY(Bsp_row == all_sprows_true.size(), "");
1469 #else
1470  MFEM_VERIFY(Bsp_row == all_sprows.size(), "");
1471 #endif
1472  }
1473  else {
1474  double* v = new double [rdim];
1475 #ifdef FULL_DOF_STENCIL
1476  for (int i = 0; i < num_sprows_true; i++) {
1477  int row = -1;
1478  if (os0 <= sprows[sprows_true[i]] && sprows[sprows_true[i]] < os1)
1479  row = fespace.GetLocalTDofNumber(sprows[sprows_true[i]] - os0);
1480 
1481  MFEM_VERIFY(row >= 0, "");
1482 
1483  if (os0 <= sprows[sprows_true[i]] && sprows[sprows_true[i]] < os1)
1484 #else
1485  for (int i = 0; i < num_sprows; i++) {
1486  int row = sprows[i];
1487  if (os0 <= row && row < os1)
1488 #endif
1489  {
1490  for (int j = 0; j < rdim; ++j)
1491  v[j] = B(row, j);
1492  MPI_Send(v, rdim, MPI_DOUBLE, 0, offsets[myid]+i, MPI_COMM_WORLD);
1493  }
1494  }
1495  delete [] v;
1496  }
1497  delete [] cts;
1498  delete [] offsets;
1499 #ifdef FULL_DOF_STENCIL
1500  delete [] allNSP;
1501  delete [] offsetSP;
1502 #endif
1503 }
1504 
1505 int SampleMeshManager::GetVariableIndex(const string variable) const
1506 {
1507  auto search = vmap.find(variable);
1508  MFEM_VERIFY(search != vmap.end(),
1509  "Variable " + variable + " is not registered!");
1510  const int var = search->second;
1511  MFEM_VERIFY(0 <= var && var < nvar, "Invalid variable index");
1512  return var;
1513 }
1514 
1516  CAROM::Matrix const& B, const int rdim, CAROM::Matrix& Bsp) const
1517 {
1518  const int var = GetVariableIndex(variable);
1519  const int s = varSpace[var];
1520 
1521  GatherDistributedMatrixRows_aux(B, rdim, spaceOS[s], spaceOS[s+1], spaceOSSP[s],
1522  *fespace[s], st2sp, sprows, all_sprows, Bsp);
1523 }
1524 
1525 void SampleMeshManager::CreateSampleMesh()
1526 {
1527  MFEM_VERIFY(nspaces > 0, "");
1528 
1529  vector<vector<int> > sample_dofs_block(nspaces); // True DOF's
1530  vector<vector<int> > sample_dofs_sub_to_sample_dofs(nspaces);
1531  vector<vector<int> > local_num_sample_dofs_sub(nspaces);
1532  vector<vector<int> > localSampleDofsToElem_sub(nspaces);
1533  vector<vector<int> > localSampleDofsToElemDof_sub(nspaces);
1534 
1535  vector<int> Ntrue(nspaces);
1536  for (int i=0; i<nspaces; ++i)
1537  Ntrue[i] = fespace[i]->TrueVSize();
1538 
1539  vector<int>& local_num_sample_dofs = num_sample_dofs_per_proc_merged;
1540 
1541  SplitDofsIntoBlocks(Ntrue, sample_dofs, local_num_sample_dofs,
1542  sample_dofs_block, sample_dofs_sub_to_sample_dofs, local_num_sample_dofs_sub);
1543 
1544  const bool getSampledEntities = !filename.empty();
1545  // Local mesh entities containing sampled DOFs, used only for ParaView visualization.
1546  set<int> intElems, faces, edges, vertices;
1547 
1548  // Find all local elements that should be included, using all spaces.
1549  GetLocalSampleMeshElements(*pmesh, *fespace[0], sample_dofs_block[0],
1550  local_num_sample_dofs_sub[0], elems,
1551  getSampledEntities, intElems, faces, edges, vertices);
1552  GetLocalDofsToLocalElementMap(*fespace[0], sample_dofs_block[0],
1553  local_num_sample_dofs_sub[0], elems, localSampleDofsToElem_sub[0],
1554  localSampleDofsToElemDof_sub[0], true);
1555  for (int i=1; i<nspaces; ++i)
1556  {
1557  set<int> elems_i;
1558  set<int> intElems_i, faces_i, edges_i,
1559  vertices_i; // Local mesh entities containing sampled DOFs, used only for ParaView visualization.
1560 
1561  GetLocalSampleMeshElements(*pmesh, *fespace[i], sample_dofs_block[i],
1562  local_num_sample_dofs_sub[i], elems_i,
1563  getSampledEntities, intElems_i, faces_i, edges_i, vertices_i);
1564  GetLocalDofsToLocalElementMap(*fespace[i], sample_dofs_block[i],
1565  local_num_sample_dofs_sub[i], elems_i, localSampleDofsToElem_sub[i],
1566  localSampleDofsToElemDof_sub[i], true);
1567 
1568  // Merge the elements found for all spaces.
1569  elems.insert(elems_i.begin(), elems_i.end());
1570 
1571  if (getSampledEntities)
1572  {
1573  intElems.insert(intElems_i.begin(), intElems_i.end());
1574  faces.insert(faces_i.begin(), faces_i.end());
1575  edges.insert(edges_i.begin(), edges_i.end());
1576  vertices.insert(vertices_i.begin(), vertices_i.end());
1577  }
1578  }
1579 
1580  vector<int> elemLocalIndices;
1581  vector<map<int, int> > elemLocalIndicesInverse;
1582  Mesh *sample_mesh = 0;
1583  BuildSampleMesh(*pmesh, fespace, elems, sample_mesh, sprows, elemLocalIndices,
1584  elemLocalIndicesInverse);
1585 
1586  MFEM_VERIFY(sample_mesh->GetNE() == elemLocalIndices.size(), "");
1587 
1588  if (myid == 0)
1589  {
1590  sample_pmesh = new ParMesh(root_comm, *sample_mesh);
1591  delete sample_mesh;
1592 
1593  // Create fespaces on sample mesh
1594  for (int i=0; i<nspaces; ++i)
1595  spfespace[i] = new ParFiniteElementSpace(sample_pmesh, fespace[i]->FEColl(),
1596  fespace[i]->GetVDim());
1597  }
1598 
1599  vector<int> spNtrue(nspaces);
1600  for (int i=0; i<nspaces; ++i)
1601  spNtrue[i] = myid == 0 ? spfespace[i]->TrueVSize() : 0;
1602 
1603  Set_s2sp(myid, nprocs, spNtrue, sample_dofs.size(), local_num_sample_dofs,
1604  local_num_sample_dofs_sub, localSampleDofsToElem_sub,
1605  localSampleDofsToElemDof_sub, sample_dofs_sub_to_sample_dofs,
1606  elemLocalIndicesInverse, spfespace, s2sp);
1607 
1608 #ifdef FULL_DOF_STENCIL
1609  Finish_s2sp_augmented(myid, nprocs, fespace, sample_dofs_block,
1610  sample_dofs_sub_to_sample_dofs, local_num_sample_dofs_sub, true, s2sp);
1611 #endif
1612 
1613  // Prepare for setting st2sp
1614 
1615  const int numStencil = sprows.size();
1616  vector<int> local_num_stencil_dofs(nprocs);
1617 
1618  MPI_Allgather(&numStencil, 1, MPI_INT, &local_num_stencil_dofs[0], 1, MPI_INT,
1619  MPI_COMM_WORLD);
1620 
1621  int* offsets = new int [nprocs];
1622  offsets[0] = 0;
1623  for (int i = 1; i < nprocs; ++i)
1624  offsets[i] = offsets[i-1] + local_num_stencil_dofs[i-1];
1625 
1626  const int total_num_stencil_dofs = offsets[nprocs-1] +
1627  local_num_stencil_dofs[nprocs-1];
1628 
1629  all_sprows.resize(total_num_stencil_dofs);
1630  MPI_Allgatherv(&sprows[0], local_num_stencil_dofs[myid],
1631  MPI_INT, &all_sprows[0], &local_num_stencil_dofs[0],
1632  offsets, MPI_INT, MPI_COMM_WORLD);
1633 
1634  // Note that all_stencil_dofs may contain DOF's on different processes that
1635  // are identical (shared DOF's), which is fine (see comments in Set_s2sp).
1636 
1637  delete [] offsets;
1638 
1639  vector<vector<int>> stencil_dofs_block(nspaces);
1640  vector<vector<int> > stencil_dofs_sub_to_stencil_dofs(nspaces);
1641  vector<vector<int> > local_num_stencil_dofs_sub(nspaces);
1642  vector<vector<int> > localStencilDofsToElem_sub(nspaces);
1643  vector<vector<int> > localStencilDofsToElemDof_sub(nspaces);
1644 
1645 #ifdef FULL_DOF_STENCIL
1646  vector<int> Nfull(nspaces);
1647  for (int i=0; i<nspaces; ++i)
1648  Nfull[i] = fespace[i]->GetVSize();
1649 
1650  SplitDofsIntoBlocks(Nfull, all_sprows, local_num_stencil_dofs,
1651  stencil_dofs_block, stencil_dofs_sub_to_stencil_dofs,
1652  local_num_stencil_dofs_sub);
1653 #else
1654  SplitDofsIntoBlocks(Ntrue, all_sprows, local_num_stencil_dofs,
1655  stencil_dofs_block, stencil_dofs_sub_to_stencil_dofs,
1656  local_num_stencil_dofs_sub);
1657 #endif
1658 
1659  for (int i=0; i<nspaces; ++i)
1660  {
1661  GetLocalDofsToLocalElementMap(*fespace[i], stencil_dofs_block[i],
1662  local_num_stencil_dofs_sub[i], elems, localStencilDofsToElem_sub[i],
1663  localStencilDofsToElemDof_sub[i], false);
1664  }
1665 
1666  Set_s2sp(myid, nprocs, spNtrue, all_sprows.size(), local_num_stencil_dofs,
1667  local_num_stencil_dofs_sub, localStencilDofsToElem_sub,
1668  localStencilDofsToElemDof_sub, stencil_dofs_sub_to_stencil_dofs,
1669  elemLocalIndicesInverse, spfespace, st2sp);
1670 
1671 #ifdef FULL_DOF_STENCIL
1672  Finish_s2sp_augmented(myid, nprocs, fespace, stencil_dofs_block,
1673  stencil_dofs_sub_to_stencil_dofs, local_num_stencil_dofs_sub, false, st2sp);
1674 #endif
1675 
1676  if (!filename.empty())
1677  {
1678  SampleVisualization(pmesh, elems, intElems, faces, edges, vertices,
1679  filename, nullptr, elemVisScale);
1680  }
1681 }
1682 
1683 void SampleVisualization(ParMesh *pmesh, set<int> const& elems,
1684  set<int> const& intElems, set<int> const& faces,
1685  set<int> const& edges, set<int> const& vertices,
1686  string const& filename, mfem::Vector *elemCount,
1687  double elementScaling)
1688 {
1689  // Write files for global pmesh and a piecewise constant function with 1 on
1690  // sample mesh elements and 0 elsewhere. Note that this is a visualization of
1691  // the sample mesh elements, not the sampled DOFs. That is, it shows all
1692  // elements that contain a sampled DOF, which could be at a vertex, edge,
1693  // face, or volume.
1694 
1695  // Construct a piecewise constant space on the global mesh.
1696  L2_FECollection l2_coll(1, pmesh->Dimension()); // order 1
1697  ParFiniteElementSpace pwc_space(pmesh, &l2_coll);
1698 
1699  ParGridFunction marker(&pwc_space);
1700  marker = 0.0;
1701  if (elemCount)
1702  {
1703  MFEM_VERIFY(elemCount->Size() == pmesh->GetNE(), "");
1704  for (int e=0; e<pmesh->GetNE(); ++e)
1705  {
1706  Array<int> dofs;
1707  pwc_space.GetElementVDofs(e, dofs);
1708 
1709  for (int i = 0; i < dofs.Size(); i++)
1710  {
1711  const int dof_i = dofs[i] >= 0 ? dofs[i] : -1 - dofs[i];
1712  marker[dof_i] = (*elemCount)[e];
1713  }
1714  }
1715  }
1716  else
1717  {
1718  for (set<int>::const_iterator it = elems.begin(); it != elems.end(); ++it)
1719  {
1720  Array<int> dofs;
1721  pwc_space.GetElementVDofs(*it, dofs);
1722 
1723  for (int i = 0; i < dofs.Size(); i++)
1724  {
1725  const int dof_i = dofs[i] >= 0 ? dofs[i] : -1 - dofs[i];
1726  marker[dof_i] = elementScaling;
1727  }
1728  }
1729  }
1730 
1731  VisItDataCollection visit_dc(filename, pmesh);
1732  visit_dc.RegisterField("Sample Elements", &marker);
1733  visit_dc.SetFormat(DataCollection::SERIAL_FORMAT);
1734  visit_dc.Save();
1735 
1736  // Write ParaView files to visualize sampled elements, edges, and vertices
1737  int ne = pmesh->GetNE();
1738  int nv = pmesh->GetNV();
1739  int nf = pmesh->GetNFaces();
1740  int nedge = pmesh->GetNEdges();
1741 
1742  Array<int> e(ne), v(nv), f(nf), edge(nedge);
1743  e = 0;
1744  v = 0;
1745  f = 0;
1746  edge = 0;
1747 
1748  for (set<int>::const_iterator it = intElems.begin(); it != intElems.end(); ++it)
1749  {
1750  e[*it] = 1;
1751  }
1752 
1753  for (set<int>::const_iterator it = faces.begin(); it != faces.end(); ++it)
1754  {
1755  f[*it] = 1;
1756  }
1757 
1758  for (set<int>::const_iterator it = edges.begin(); it != edges.end(); ++it)
1759  {
1760  edge[*it] = 1;
1761  }
1762 
1763  for (set<int>::const_iterator it = vertices.begin(); it != vertices.end(); ++it)
1764  {
1765  v[*it] = 1;
1766  }
1767 
1768  /*
1769  for (int i=0; i<ne/2; ++i) { e[i] = 0; }
1770  for (int i=0; i<nv/2; ++i) { v[i] = 0; }
1771  for (int i=0; i<nf/2; ++i) { f[i] = 0; }
1772  for (int i=0; i<nedge/2; ++i) { edge[i] = 0; }
1773  */
1774 
1775  ParaViewPrintAttributes(filename + "_pv", *pmesh, pmesh->Dimension(), &e, &v);
1776  if (pmesh->Dimension() == 3)
1777  {
1778  ParaViewPrintAttributes(filename + "_pv_face", *pmesh, 2, &f, &v);
1779  }
1780  ParaViewPrintAttributes(filename + "_pv_edge", *pmesh, 1, &edge, &v);
1781 }
1782 
1783 void SampleDOFSelector::ReadMapFromFile(const string variable, string file_name)
1784 {
1785  {
1786  auto search = vmap.find(variable);
1787  MFEM_VERIFY(search == vmap.end(),
1788  "Map for variable " + variable + " is already read!");
1789  }
1790  vmap[variable] = nvar;
1791 
1792  vector<int> v;
1793  ifstream file;
1794  file.open(file_name);
1795  string line;
1796  while(getline(file, line)) {
1797  v.push_back(stoi(line));
1798  }
1799  file.close();
1800 
1801  s2sp_var.push_back(v);
1802  nvar = s2sp_var.size();
1803 }
1804 
1805 int SampleDOFSelector::GetVariableIndex(const string variable) const
1806 {
1807  auto search = vmap.find(variable);
1808  MFEM_VERIFY(search != vmap.end(),
1809  "Variable " + variable + " is not registered!");
1810  const int var = search->second;
1811  MFEM_VERIFY(0 <= var && var < nvar, "Invalid variable index");
1812  return var;
1813 }
1814 
1815 void SampleDOFSelector::GetSampledValues(const string variable,
1816  mfem::Vector const& v, CAROM::Vector & s) const
1817 {
1818  const int var = GetVariableIndex(variable);
1819 
1820  const int n = s2sp_var[var].size();
1821  MFEM_VERIFY(s.dim() == n, "");
1822  for (int i=0; i<n; ++i)
1823  s(i) = v[s2sp_var[var][i]];
1824 }
1825 
1826 } // namespace CAROM
int numColumns() const
Returns the number of columns in the Matrix. This method will return the same value from each process...
Definition: Matrix.h:226
int numRows() const
Returns the number of rows of the Matrix on this processor.
Definition: Matrix.h:200
void ReadMapFromFile(const string variable, string file_name)
Reads a variable sample DOF map from file, written by SampleMeshManager::WriteVariableSampleMap,...
void GetSampledValues(const string variable, mfem::Vector const &v, CAROM::Vector &s) const
Sets a vector of sampled DOFs from a vector on the sample mesh space. Note that this function is the ...
void GetSampledValues(const string variable, mfem::Vector const &v, CAROM::Vector &s) const
Sets a vector of sampled DOFs from a vector on the sample mesh space.
void RegisterSampledVariable(const string variable, const int space, vector< int > const &sample_dofs_v, vector< int > const &num_sample_dofs_per_proc)
Register a variable and set its sampled DOFs.
void ConstructSampleMesh()
Construct the sample mesh, after registering all sampled variables.
SampleMeshManager(vector< ParFiniteElementSpace * > &fespace_, string visFileName="", double visScale=1.0)
Constructor creating a SampleMeshManager with arbitrarily many ParFiniteElementSpaces on a full-order...
int GetNumVarSamples(const string variable) const
Returns the number of sampled DOFs on all processes for a variable.
void WriteVariableSampleMap(const string variable, string file_name) const
Writes a variable sample DOF map to file, which can be read by SampleDOFSelector::ReadMapFromFile in ...
void GatherDistributedMatrixRows(const string variable, CAROM::Matrix const &B, const int rdim, CAROM::Matrix &Bsp) const
Gather the rows of a distributed basis CAROM::Matrix corresponding to a sample mesh space.
int dim() const
Returns the dimension of the Vector on this processor.
Definition: Vector.h:251