version 3.10-dev
structuredlatticegridcreator.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
12#ifndef DUMUX_IO_STRUCTURED_LATTICE_GRID_CREATOR_HH
13#define DUMUX_IO_STRUCTURED_LATTICE_GRID_CREATOR_HH
14
15#if HAVE_DUNE_FOAMGRID
16
17#include <vector>
18#include <memory>
19#include <type_traits>
20#include <random>
21
22#include <dune/common/concept.hh>
23#include <dune/common/exceptions.hh>
24#include <dune/grid/common/gridfactory.hh>
25#include <dune/grid/yaspgrid.hh>
26#include <dune/geometry/referenceelements.hh>
27
28// FoamGrid specific includes
29#include <dune/foamgrid/foamgrid.hh>
30#include <dune/foamgrid/dgffoam.hh>
31
35
36namespace Dumux::PoreNetwork {
37
38namespace Concept {
39
44template<class GlobalPosition>
45struct LowDimElementSelector
46{
47 template<class F>
48 auto require(F&& f) -> decltype(
49 bool(f(std::declval<const GlobalPosition&>(), std::declval<const GlobalPosition&>()))
50 );
51};
52} // end namespace Concept
53
58template<int dimWorld>
59class StructuredLatticeGridCreator
60{
61 using GridType = Dune::FoamGrid<1, dimWorld>;
62 using Element = typename GridType::template Codim<0>::Entity;
63 using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
64 using CoordScalar = typename GridType::ctype;
66 using HostGridView = typename HostGrid::LeafGridView;
67 using ReferenceElements = typename Dune::ReferenceElements<CoordScalar, dimWorld>;
68 // grid factory expects unsigned int, will yield compiler warning for std::size_t
69 using VertexIndexPair = std::pair<unsigned int, unsigned int>;
70
71 struct Diagonal
72 {
73 VertexIndexPair localVertexIndices;
74 unsigned int directionNumber;
75 };
76public:
77
78 using Grid = GridType;
79
80 void init(const std::string& paramGroup = "")
81 {
82 auto useAllElements = [](const GlobalPosition& a, const GlobalPosition& b){ return true; };
83 init(useAllElements, paramGroup);
84 }
85
86 template<class LowDimElementSelector, // cppcheck-suppress syntaxError
87 typename std::enable_if_t<Dune::models<Concept::LowDimElementSelector<GlobalPosition>, LowDimElementSelector>(), int> = 0>
88 void init(const LowDimElementSelector& lowDimElementSelector,
89 const std::string& paramGroup = "")
90 {
91 paramGroup_ = paramGroup;
92 removeThroatsOnBoundary_ = getParamFromGroup<std::vector<std::size_t>>(paramGroup, "Grid.RemoveThroatsOnBoundary",
93 std::vector<std::size_t>());
94
95 setElementSelector_(lowDimElementSelector);
96 initRandomNumberGenerator_();
97
98 // create the host grid
99 const auto hostGridInputData = getHostGridInputData_();
101 using HastGridManager = GridManager<HostGrid>;
102 HastGridManager hostGridManager;
103 hostGridManager.init(hostGridInputData.positions, hostGridInputData.cells, hostGridInputData.grading, paramGroup_);
104 hostGridView_ = std::make_unique<HostGridView>(hostGridManager.grid().leafGridView());
105
106 hostGridLowerLeft_ = hostGridInputData.lowerLeft;
107 hostGridUpperRight_ = hostGridInputData.upperRight;
108
109 convertHostGridToLowDimGrid_();
110 }
111
115 void loadBalance()
116 {
117 if (Dune::MPIHelper::getCommunication().size() > 1)
118 gridPtr_->loadBalance();
119 }
120
124 Grid& grid()
125 { return *gridPtr(); }
126
130 std::shared_ptr<Grid>& gridPtr()
131 { return gridPtr_; }
132
133private:
134
135 template<class LowDimElementSelector>
136 void setElementSelector_(const LowDimElementSelector& lowDimElementSelector)
137 {
138 if (!removeThroatsOnBoundary_.empty())
139 {
140 auto finalSelector = [&](const GlobalPosition& a, const GlobalPosition& b)
141 {
142 static const auto lambdaToRemoveThroatsOnBoundary = getLambdaToRemoveThroatsOnBoundary_();
143 using std::min;
144 return min(lambdaToRemoveThroatsOnBoundary(a, b), lowDimElementSelector(a, b));
145 };
146 considerLowDimElement_ = finalSelector;
147 }
148 else
149 considerLowDimElement_ = lowDimElementSelector;
150 }
151
152 void initRandomNumberGenerator_()
153 {
154 directionProbability_ = getDirectionProbability_();
155
156 if (hasParamInGroup(paramGroup_, "Grid.DeletionRandomNumberSeed"))
157 {
158 const auto seed = getParamFromGroup<std::size_t>(paramGroup_, "Grid.DeletionRandomNumberSeed");
159 generator_.seed(seed);
160 }
161 else
162 {
163 std::random_device rd;
164 generator_.seed(rd());
165 }
166 }
167
168 void convertHostGridToLowDimGrid_()
169 {
170 resizeContainers_();
171
172 for (const auto& element : elements(*hostGridView_))
173 {
174 const auto geometry = element.geometry();
175 const auto refElement = ReferenceElements::general(geometry.type());
176 treatEdges_(element, refElement, geometry);
177 treatDiagonals_(element, refElement, geometry);
178 }
179 if (elementSet_.empty())
180 DUNE_THROW(Dune::GridError, "Trying to create pore network with zero elements!");
181
182 // make the actual network grid
183 Dune::GridFactory<Grid> factory;
184 for (auto&& vertex : vertexSet_)
185 factory.insertVertex(vertex);
186 for (auto&& element : elementSet_)
187 factory.insertElement(Dune::GeometryTypes::cube(1), {element.first, element.second});
188
189 gridPtr_ = std::shared_ptr<Grid>(factory.createGrid());
190 }
191
192 void resizeContainers_()
193 {
194 vertexInserted_.resize(hostGridView_->size(dimWorld), false);
195 hostVertexIdxToVertexIdx_.resize(hostGridView_->size(dimWorld));
196 edgeInserted_.resize(hostGridView_->size(dimWorld-1), false);
197 faceInserted_.resize(hostGridView_->size(dimWorld-2), false);
198 }
199
200 template<class HostGridElement>
201 bool maybeInsertElementAndVertices_(const HostGridElement& hostGridElement,
202 const typename HostGridElement::Geometry& hostGridElementGeometry,
203 std::size_t localVertexIdx0,
204 std::size_t localVertexIdx1,
205 double directionProbability)
206 {
207 if (neglectElement_(hostGridElementGeometry, localVertexIdx0, localVertexIdx1, directionProbability))
208 return false;
209 else
210 {
211 insertElementAndVertices_(hostGridElement, hostGridElementGeometry, localVertexIdx0, localVertexIdx1, directionProbability);
212 return true;
213 }
214 }
215
216 template<class HostGridElement>
217 void insertElementAndVertices_(const HostGridElement& hostGridElement,
218 const typename HostGridElement::Geometry& hostGridElementGeometry,
219 std::size_t localVertexIdx0,
220 std::size_t localVertexIdx1,
221 double directionProbability)
222 {
223 // get global vertex indices w.r.t. host grid
224 const auto vIdxGlobal0 = hostGridView_->indexSet().subIndex(hostGridElement, localVertexIdx0, dimWorld);
225 const auto vIdxGlobal1 = hostGridView_->indexSet().subIndex(hostGridElement, localVertexIdx1, dimWorld);
226
227 auto getGobalVertexIdx = [&](auto globalIdx, auto localIdx)
228 {
229 if (!vertexInserted_[globalIdx])
230 {
231 vertexInserted_[globalIdx] = true;
232 hostVertexIdxToVertexIdx_[globalIdx] = vertexSet_.size();
233 vertexSet_.push_back(hostGridElementGeometry.corner(localIdx));
234 }
235
236 return hostVertexIdxToVertexIdx_[globalIdx];
237 };
238
239 const auto newVertexIdx0 = getGobalVertexIdx(vIdxGlobal0, localVertexIdx0);
240 const auto newVertexIdx1 = getGobalVertexIdx(vIdxGlobal1, localVertexIdx1);
241 elementSet_.emplace_back(newVertexIdx0, newVertexIdx1);
242 }
243
244 auto getDirectionProbability_() const
245 {
246 std::array<double, numDirections_()> directionProbability;
247 directionProbability.fill(0.0); // do not delete any throats
248
249 // convenience option to delete all diagonal throats
250 if (getParamFromGroup<bool>(paramGroup_, "Grid.RegularLattice", false))
251 {
252 directionProbability.fill(1.0); // delete all throats ...
253 for (int i = 0; i < dimWorld; ++i)
254 directionProbability[i] = 0.0; // ... but not the ones parallel to the main axes
255
256 return directionProbability;
257 }
258
259 // get user input or print out help message explaining correct usage
260 if (hasParamInGroup(paramGroup_, "Grid.DeletionProbability"))
261 {
262 try
263 {
264 directionProbability = getParamFromGroup<decltype(directionProbability)>(paramGroup_, "Grid.DeletionProbability");
265 }
267 {
268 throwDirectionError_();
269 }
270 }
271
272 return directionProbability;
273 }
274
275 void throwDirectionError_() const
276 {
277 // define directions (to be used for user specified anisotropy)
278 Dune::FieldVector<std::string, numDirections_()> directions;
279 if (dimWorld < 3) // 2D
280 {
281 // x, y
282 directions[0] = "1: (1, 0)\n";
283 directions[1] = "2: (0, 1)\n";
284 // diagonals through cell midpoint
285 directions[2] = "3: (1, 1)\n";
286 directions[3] = "4: (1, -1)\n";
287 }
288 else // 3D
289 {
290 // x, y, z
291 directions[0] = " 1: (1, 0, 0)\n";
292 directions[1] = "2: (0, 1, 0)\n";
293 directions[2] = "3: (0, 0, 1)\n";
294 //face diagonals
295 directions[3] = "4: (1, 1, 0)\n";
296 directions[4] = "5: (1, -1, 0)\n";
297 directions[5] = "6: (1, 0, 1)\n";
298 directions[6] = "7: (1, 0, -1)\n";
299 directions[7] = "8: (0, 1, 1)\n";
300 directions[8] = "9: (0, 1, -1)\n";
301 // diagonals through cell midpoint
302 directions[9] = "10: (1, 1, 1)\n";
303 directions[10] = "11: (1, 1, -1)\n";
304 directions[11] = "12: (-1, 1, 1)\n";
305 directions[12] = "13: (-1, -1, 1)\n";
306 }
307 DUNE_THROW(ParameterException, "You must specify probabilities for all directions (" << numDirections_() << ") \n" << directions << "\nExample (3D):\n\n"
308 << "DeletionProbability = 0.5 0.5 0 0 0 0 0 0 0 0 0 0 0 \n\n"
309 << "deletes approximately 50% of all throats in x and y direction, while no deletion in any other direction takes place.\n" );
310 }
311
312 static constexpr std::size_t numDirections_()
313 { return (dimWorld < 3) ? 4 : 13; }
314
315 template<class Geometry>
316 bool neglectElement_(Geometry& hostGridElementGeometry,
317 std::size_t localVertexIdx0,
318 std::size_t localVertexIdx1,
319 double directionProbability)
320 {
321 if (randomNumer_() < directionProbability)
322 return true;
323
324 // TODO Change order of execution: check considerLowDimElement_ before checking the random number
325 // TODO This change will alter the reference solution because randomNumer_() gets called less, therefore the random numbers are different
326 if (!considerLowDimElement_(hostGridElementGeometry.corner(localVertexIdx0), hostGridElementGeometry.corner(localVertexIdx1)))
327 return true;
328
329 return false;
330 }
331
332 auto randomNumer_()
333 { return uniformdist_(generator_); }
334
335 auto getHostGridInputData_() const
336 {
337 struct InputData
338 {
339 std::array<std::vector<int>, dimWorld> cells;
340 std::array<std::vector<CoordScalar>, dimWorld> positions;
341 std::array<std::vector<CoordScalar>, dimWorld> grading;
342 GlobalPosition lowerLeft;
343 GlobalPosition upperRight;
344 } inputData;
345
346 // convenience references
347 auto& cells = inputData.cells;
348 auto& positions = inputData.positions;
349 auto& grading = inputData.grading;
350 auto& lowerLeft = inputData.lowerLeft;
351 auto& upperRight = inputData.upperRight;
352
353 // try to get the pore positions explicitly ...
354 for (int i = 0; i < dimWorld; ++i)
355 positions[i] = getParamFromGroup<std::vector<CoordScalar>>(paramGroup_, "Grid.Positions" + std::to_string(i), std::vector<CoordScalar>{});
356 if (std::none_of(positions.begin(), positions.end(), [&](auto& v){ return v.empty(); }))
357 {
358 for (int i = 0; i < dimWorld; ++i)
359 {
360 cells[i].resize(positions[i].size()-1, 1.0);
361 grading[i].resize(positions[i].size()-1, 1.0);
362 }
363 }
364 else // .. or calculate positions from input data
365 {
366 const auto lowerLeft = getParamFromGroup<GlobalPosition>(paramGroup_, "Grid.LowerLeft", GlobalPosition(0.0));
367 const auto upperRight = getParamFromGroup<GlobalPosition>(paramGroup_, "Grid.UpperRight");
368 const auto numPores = getParamFromGroup<std::vector<unsigned int>>(paramGroup_, "Grid.NumPores");
369 if (numPores.size() != dimWorld)
370 DUNE_THROW(ParameterException, "Grid.NumPores has to be a space-separated list of " << dimWorld << " integers!");
371
372 for (int i = 0; i < dimWorld; ++i)
373 {
374 positions[i].push_back(lowerLeft[i]);
375 positions[i].push_back(upperRight[i]);
376 cells[i].push_back(numPores[i] - 1);
377 grading[i].resize(positions[i].size()-1, 1.0);
378 grading[i] = getParamFromGroup<std::vector<CoordScalar>>(paramGroup_, "Grid.Grading" + std::to_string(i), grading[i]);
379 }
380 }
381
382 // get the lower left position
383 lowerLeft = [&]()
384 {
385 GlobalPosition result;
386 for (int i = 0; i < dimWorld; ++i)
387 result[i] = positions[i][0];
388 return result;
389 }();
390
391 // get the upper right position
392 upperRight = [&]()
393 {
394 GlobalPosition result;
395 for (int i = 0; i < dimWorld; ++i)
396 result[i] = positions[i].back();
397 return result;
398 }();
399
400 return inputData;
401 }
402
403 template<class HostGridElement, class ReferenceElement, class Geometry>
404 void treatEdges_(const HostGridElement& element, const ReferenceElement& refElement, const Geometry& geometry)
405 {
406 // go over all edges and add them as elements if they passed all the tests
407 for (unsigned int edgeIdx = 0; edgeIdx < element.subEntities(dimWorld-1); ++edgeIdx)
408 {
409 const auto vIdxLocal0 = refElement.subEntity(edgeIdx, dimWorld-1, 0, dimWorld);
410 const auto vIdxLocal1 = refElement.subEntity(edgeIdx, dimWorld-1, 1, dimWorld);
411 const auto edgeIdxGlobal = hostGridView_->indexSet().subIndex(element, edgeIdx, dimWorld-1);
412
413 if (edgeInserted_[edgeIdxGlobal])
414 continue;
415 else
416 edgeInserted_[edgeIdxGlobal] = true;
417
418 std::size_t directionNumber = 0;
419
420 if(dimWorld == 2 ) // 2D
421 {
422 if(edgeIdx < 2) // y-direction
423 directionNumber = 0;
424 else // x-direction
425 directionNumber = 1;
426 }
427 else // 3D
428 {
429 if(edgeIdx < 4) // z-direction
430 directionNumber = 2;
431 else if(edgeIdx == 4 || edgeIdx == 5 || edgeIdx == 8 || edgeIdx == 9) // y-direction
432 directionNumber = 1;
433 else if(edgeIdx == 6 || edgeIdx == 7 || edgeIdx == 10 || edgeIdx == 11) // x-direction
434 directionNumber = 0;
435 }
436 maybeInsertElementAndVertices_(element, geometry, vIdxLocal0, vIdxLocal1, directionProbability_[directionNumber]);
437 }
438 }
439
440 template<class HostGridElement, class ReferenceElement, class Geometry>
441 void treatDiagonals_(const HostGridElement& element, const ReferenceElement& refElement, const Geometry& geometry)
442 {
443 if constexpr (dimWorld < 3)
444 treatDiagonalConnections2D_(element, geometry);
445 else
446 treatDiagonalConnections3D_(element, refElement, geometry);
447 }
448
449 template<class HostGridElement, class Geometry>
450 void treatDiagonalConnections2D_(const HostGridElement& element,
451 const Geometry& geometry)
452 {
453 static constexpr std::array<Diagonal, 2> diagonals{ Diagonal{std::make_pair(0, 3), 2},
454 Diagonal{std::make_pair(1, 2), 3} };
455
456 treatIntersectingDiagonals_(element, geometry, diagonals);
457 }
458
459 template<class HostGridElement, class ReferenceElement, class Geometry>
460 void treatDiagonalConnections3D_(const HostGridElement& element,
461 const ReferenceElement& refElement,
462 const Geometry& geometry)
463 {
464 // set diagonals on host grid element faces
465 for (auto faceIdx = 0; faceIdx < element.subEntities(dimWorld-2); ++faceIdx)
466 {
467 const auto faceIdxGlobal = hostGridView_->indexSet().subIndex(element, faceIdx, dimWorld-2);
468 // face already checked?
469 if (faceInserted_[faceIdxGlobal])
470 continue;
471 else
472 faceInserted_[faceIdxGlobal] = true;
473
474 // get local vertex indices
475 std::array<unsigned int, 4> vIdxLocal;
476 for (int i = 0; i < 4; i++)
477 vIdxLocal[i] = refElement.subEntity(faceIdx, dimWorld-2, i, dimWorld);
478
479 const auto directionNumbers = [&]()
480 {
481 if (faceIdx < 2)
482 return std::make_pair<unsigned int, unsigned int>(8,7);
483 else if (faceIdx < 4)
484 return std::make_pair<unsigned int, unsigned int>(6,5);
485 else
486 return std::make_pair<unsigned int, unsigned int>(4,3);
487 }();
488
489 const std::array<Diagonal, 2> diagonals{ Diagonal{std::make_pair(vIdxLocal[1], vIdxLocal[2]), directionNumbers.first},
490 Diagonal{std::make_pair(vIdxLocal[0], vIdxLocal[3]), directionNumbers.second} };
491
492 treatIntersectingDiagonals_(element, geometry, diagonals);
493 }
494
495 static constexpr std::array<Diagonal, 4> diagonals{ Diagonal{std::make_pair(0, 7), 9},
496 Diagonal{std::make_pair(3, 4), 10},
497 Diagonal{std::make_pair(1, 6), 11},
498 Diagonal{std::make_pair(2, 5), 12}, };
499
500 treatIntersectingDiagonals_(element, geometry, diagonals);
501 }
502
503 template<class HostGridElement, class Geometry, class Diagonals>
504 void treatIntersectingDiagonals_(const HostGridElement& element,
505 const Geometry& geometry,
506 const Diagonals& diagonals)
507 {
508 static const bool allowIntersectingDiagonals = getParamFromGroup<bool>(paramGroup_, "Grid.AllowIntersectingDiagonals", true);
509
510 auto treat = [&](const Diagonal& diagonal)
511 {
512 return maybeInsertElementAndVertices_(element, geometry,
513 diagonal.localVertexIndices.first, diagonal.localVertexIndices.second,
514 directionProbability_[diagonal.directionNumber]);
515 };
516
517 if (allowIntersectingDiagonals)
518 {
519 // insert all diagonals
520 for (const auto& diagonal : diagonals)
521 treat(diagonal);
522 }
523 else
524 {
525 auto order = createOrderedList_(diagonals.size());
526 std::shuffle(order.begin(), order.end(), generator_);
527 for (auto i : order)
528 {
529 const auto& diagonal = diagonals[i];
530 const bool inserted = treat(diagonal);
531 if (inserted)
532 return;
533 }
534 }
535 }
536
537 std::vector<std::size_t> createOrderedList_(const std::size_t size) const
538 {
539 std::vector<std::size_t> result(size);
540 std::iota(result.begin(), result.end(), 0);
541 return result;
542 }
543
544 auto getLambdaToRemoveThroatsOnBoundary_() const
545 {
546 auto negletElementsOnBoundary = [&](const GlobalPosition& a, const GlobalPosition& b)
547 {
548 const auto center = 0.5 * (a + b);
549 const auto eps = (a-b).two_norm() * 1e-5;
550
551 bool neglectElement = false;
552 for (auto i : removeThroatsOnBoundary_)
553 {
554 switch(i)
555 {
556 case 0: neglectElement = center[0] < hostGridLowerLeft_[0] + eps; break;
557 case 1: neglectElement = center[0] > hostGridUpperRight_[0] - eps; break;
558 case 2: if constexpr (dimWorld > 1)
559 {
560 neglectElement = center[1] < hostGridLowerLeft_[1] + eps;
561 break;
562 }
563 case 3: if constexpr (dimWorld > 1)
564 {
565 neglectElement = center[1] > hostGridUpperRight_[1] - eps;
566 break;
567 }
568 case 4: if constexpr (dimWorld > 2)
569 {
570 neglectElement = center[2] < hostGridLowerLeft_[2] + eps;
571 break;
572 }
573 case 5: if constexpr (dimWorld > 2)
574 {
575 neglectElement = center[2] > hostGridUpperRight_[2] - eps;
576 break;
577 }
578 }
579
580 if (neglectElement)
581 return false;
582 }
583
584 return true;
585 };
586
587 return negletElementsOnBoundary;
588 }
589
590 std::string paramGroup_;
591 GlobalPosition hostGridLowerLeft_;
592 GlobalPosition hostGridUpperRight_;
593 std::vector<std::size_t> removeThroatsOnBoundary_;
594 std::unique_ptr<const HostGridView> hostGridView_;
595 std::function<bool(const GlobalPosition&, const GlobalPosition&)> considerLowDimElement_;
596
597 std::vector<GlobalPosition> vertexSet_;
598 std::vector<VertexIndexPair> elementSet_;
599
600 std::vector<bool> vertexInserted_;
601 std::vector<std::size_t> hostVertexIdxToVertexIdx_;
602 std::vector<std::size_t> edgeInserted_;
603 std::vector<std::size_t> faceInserted_;
604
605 mutable std::mt19937 generator_;
606 std::uniform_real_distribution<> uniformdist_{0, 1};
607 std::array<double, numDirections_()> directionProbability_;
608
609 std::shared_ptr<Grid> gridPtr_;
610};
611
612} // end namespace Dumux::PoreNetwork
613
614#endif // HAVE_DUNE_FOAMGRID
615
616#endif
Exception thrown if a run-time parameter is not specified correctly.
Definition: exceptions.hh:48
Definition: consistentlyorientedgrid.hh:20
Some exceptions thrown in DuMux
Grid manager specialization for YaspGrid.
Corners::value_type center(const Corners &corners)
The center of a given list of corners.
Definition: center.hh:24
T getParamFromGroup(Args &&... args)
A free function to get a parameter from the parameter tree singleton with a model group.
Definition: parameters.hh:149
bool hasParamInGroup(const std::string &paramGroup, const std::string &param)
Check whether a key exists in the parameter tree with a model group prefix.
Definition: parameters.hh:165
Definition: discretization/porenetwork/fvelementgeometry.hh:24
The infrastructure to retrieve run-time parameters from Dune::ParameterTrees.