Chaste Commit::6e4f5fe395bca70eb7641cf6e0e87f450383ca5a
ImmersedBoundaryMesh.cpp
1/*
2
3Copyright (c) 2005-2026, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, are permitted provided that the following conditions are met:
14 * Redistributions of source code must retain the above copyright notice,
15 this list of conditions and the following disclaimer.
16 * Redistributions in binary form must reproduce the above copyright notice,
17 this list of conditions and the following disclaimer in the documentation
18 and/or other materials provided with the distribution.
19 * Neither the name of the University of Oxford nor the names of its
20 contributors may be used to endorse or promote products derived from this
21 software without specific prior written permission.
22
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34*/
35
36#include "ImmersedBoundaryMesh.hpp"
37
38#include <algorithm>
39#include <cmath>
40
41#include "ImmersedBoundaryEnumerations.hpp"
43#include "RandomNumberGenerator.hpp"
45#include "Warnings.hpp"
46
47
48template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
52 unsigned numGridPtsX,
53 unsigned numGridPtsY)
54 : mNumGridPtsX(numGridPtsX),
55 mNumGridPtsY(numGridPtsY),
56 mCharacteristicNodeSpacing(DOUBLE_UNSET),
57 mElementDivisionSpacing(DOUBLE_UNSET),
58 mNeighbourDist(0.1),
59 mSummaryOfNodeLocations(0.0)
60{
61 // Clear mNodes and mElements
62 Clear();
63
64 switch (SPACE_DIM)
65 {
66 case 2:
67 m2dVelocityGrids.resize(extents[2][mNumGridPtsX][mNumGridPtsY]);
68 break;
69
70 case 3:
71 EXCEPTION("Not implemented yet in 3D");
72 break;
73
74 //LCOV_EXCL_START
75 default:
77 break;
78 //LCOV_EXCL_STOP
79 }
80
81 // Populate mNodes, mElements, and mLaminas
82 for (unsigned node_it = 0; node_it < nodes.size(); node_it++)
83 {
84 Node<SPACE_DIM>* p_temp_node = nodes[node_it];
85 this->mNodes.push_back(p_temp_node);
86 }
87 for (unsigned elem_it = 0; elem_it < elements.size(); elem_it++)
88 {
89 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_temp_element = elements[elem_it];
90 mElements.push_back(p_temp_element);
91 }
92 for (unsigned lam_it = 0; lam_it < laminas.size(); lam_it++)
93 {
94 ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>* p_temp_lamina = laminas[lam_it];
95 mLaminas.push_back(p_temp_lamina);
96 }
97
98 // Register elements with nodes
99 for (unsigned elem_it = 0; elem_it < mElements.size(); elem_it++)
100 {
102
103 unsigned element_index = p_element->GetIndex();
104 unsigned num_nodes_in_element = p_element->GetNumNodes();
105
106 for (unsigned node_idx = 0; node_idx < num_nodes_in_element; node_idx++)
107 {
108 p_element->GetNode(node_idx)->AddElement(element_index);
109 }
110 }
111
112 // Register laminas with nodes
113 //\todo is there a way we can register laminas with nodes?
114
115 // Set characteristic node spacing to the average distance between nodes in elements
116 double total_perimeter = 0.0;
117 unsigned total_nodes = 0;
118 for (unsigned elem_it = 0; elem_it < mElements.size(); elem_it++)
119 {
120 total_perimeter += this->GetSurfaceAreaOfElement(elem_it);
121 total_nodes += mElements[elem_it]->GetNumNodes();
122 }
123 mCharacteristicNodeSpacing = total_perimeter / double(total_nodes);
124
125 // Position fluid sources at the centroid of each element, and set strength to zero
126 for (unsigned elem_it = 0; elem_it < elements.size(); elem_it++)
127 {
128 unsigned elem_idx = mElements[elem_it]->GetIndex();
129
130 // Create a new fluid source at the correct location for each element
131 unsigned source_idx = static_cast<unsigned>(mElementFluidSources.size());
132 c_vector<double, SPACE_DIM> source_location = this->GetCentroidOfElement(elem_idx);
133 mElementFluidSources.push_back(std::make_shared<FluidSource<SPACE_DIM>>(source_idx, source_location));
134
135 // Set source parameters
136 mElementFluidSources.back()->SetAssociatedElementIndex(elem_idx);
137 mElementFluidSources.back()->SetStrength(0.0);
138
139 // Associate source with element
140 mElements[elem_it]->SetFluidSource(mElementFluidSources.back());
141 }
142
143 //Set up a number of sources to balance any active sources associated with elements
144 double balancing_source_spacing = 2.0 * mCharacteristicNodeSpacing;
145
146 // We start at the characteristic spacing in from the left-hand end, and place a source every 2 spacings
147 double current_location = mCharacteristicNodeSpacing;
148
149 while (current_location < 1.0)
150 {
151 // Create a new fluid source at the current x-location and zero y-location
152 unsigned source_idx = static_cast<unsigned>(mBalancingFluidSources.size());
153 mBalancingFluidSources.push_back(std::make_shared<FluidSource<SPACE_DIM>>(source_idx, current_location));
154
155 mBalancingFluidSources.back()->SetStrength(0.0);
156
157 // Increment the current location
158 current_location += balancing_source_spacing;
159 }
160
161 // Calculate a default neighbour dist, as half the root of the average element volume
162 constexpr double power = 1.0 / SPACE_DIM;
163 const double total_volume_of_elems = std::accumulate(mElements.begin(), mElements.end(), 0.0,
165 {
166 return d + this->GetVolumeOfElement(a->GetIndex());
167 });
168 mNeighbourDist = 0.5 * std::pow(total_volume_of_elems / mElements.size(), power);
169
170 this->mMeshChangesDuringSimulation = true;
172
173template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
175 [[maybe_unused]] unsigned index) // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
176{
177 if constexpr (SPACE_DIM == 2)
178 {
179 c_vector<double, 3> moments = CalculateMomentsOfElement(index);
180
181 double discriminant = sqrt((moments(0) - moments(1)) * (moments(0) - moments(1)) + 4.0 * moments(2) * moments(2));
182
183 // Note that as the matrix of second moments of area is symmetric, both its eigenvalues are real
184 double largest_eigenvalue = (moments(0) + moments(1) + discriminant) * 0.5;
185 double smallest_eigenvalue = (moments(0) + moments(1) - discriminant) * 0.5;
186
187 double elongation_shape_factor = sqrt(largest_eigenvalue / smallest_eigenvalue);
188 return elongation_shape_factor;
189 }
190 else
191 {
193 }
194}
195
196bool CustomComparisonForVectorX(c_vector<double, 2> vecA, c_vector<double, 2> vecB)
197{
198 return vecA[0] < vecB[0];
199}
200
201template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
203{
204 if constexpr (SPACE_DIM == 2)
205 {
206 double total_length = 0.0;
207
208 // Get the current elements
209 std::vector<c_vector<double, 2> > centroids(mElements.size());
210 for (unsigned elem_it = 0; elem_it < mElements.size(); elem_it++)
211 {
212 centroids[elem_it] = this->GetCentroidOfElement(mElements[elem_it]->GetIndex());
213 }
214
215 // Sort centroids by X
216 std::sort(centroids.begin(), centroids.end(), CustomComparisonForVectorX);
217
218 // Calculate piecewise linear length connecting centroids
219 for (unsigned cent_it = 1; cent_it < centroids.size(); cent_it++)
220 {
221 total_length += norm_2(centroids[cent_it - 1] - centroids[cent_it]);
222 }
223
224 double straight_line_length = norm_2(centroids[0] - centroids[centroids.size() - 1]);
225
226 // Avoid division by zero
227 if (straight_line_length < 0.00001)
228 {
229 return 0;
230 }
231
232 return total_length / straight_line_length;
233 }
234 else
235 {
237 }
239
240bool CustomComparisonForSkewnessMeasure(std::pair<unsigned, c_vector<double, 2> > pairA, std::pair<unsigned, c_vector<double, 2> > pairB)
241{
242 return pairA.second[0] < pairB.second[0];
243}
244
245template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
247 [[maybe_unused]] unsigned elemIndex, // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
248 c_vector<double, SPACE_DIM> axis)
249{
250 /*
251 * Method outline:
252 *
253 * Given an arbitrary axis and a closed polygon, we calculate the skewness of the mass distribution of the polygon
254 * perpendicular to the axis. This is used as a measure of asymmetry.
255 *
256 * To simplify calculating the mass distribution, we translate the centroid of the element to the origin and rotate
257 * about the centroid so the axis is vertical; then we sort all the nodes in ascending order of their x-coordinate.
258 *
259 * For each node in order, we need to know the length of the intersection through the node of the vertical line with
260 * the polygon. Once calculated, we have a piecewise-linear PDF for the mass distribution, which can be normalised
261 * by the surface area of the polygon.
262 *
263 * By integrating the pdf directly, we can calculate exactly the necessary moments of the distribution needed for
264 * the skewness.
265 *
266 * This method does not work for concave shapes, and emits a warning but continues anyway. Results will be close
267 * to correct for mildly concave shapes or may be correct depending on the alignment of the element.
268 */
269
270 // This method only works in 2D
271 if constexpr (SPACE_DIM == 2 && ELEMENT_DIM == 2)
272 {
273 // Get relevant info about the element
274 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_elem = this->GetElement(elemIndex);
275 unsigned num_nodes = p_elem->GetNumNodes();
276 double area_of_elem = this->GetVolumeOfElement(elemIndex);
277 c_vector<double, SPACE_DIM> centroid = this->GetCentroidOfElement(elemIndex);
278
279 // Get the unit axis and trig terms for rotation
280 c_vector<double, SPACE_DIM> unit_axis = axis / norm_2(axis);
281 double sin_theta = unit_axis[0];
282 double cos_theta = unit_axis[1];
283
284 // We need the (rotated) node locations in two orders - original and ordered left-to-right.
285 // For the latter we need to keep track of index, so we store that as part of a pair.
286 std::vector<c_vector<double, SPACE_DIM> > node_locations_original_order;
287 std::vector<std::pair<unsigned, c_vector<double, SPACE_DIM> > > ordered_locations;
288
289 // Get the node locations of the current element relative to its centroid, and rotate them
290 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
291 {
292 const c_vector<double, SPACE_DIM>& node_location = p_elem->GetNode(node_idx)->rGetLocation();
293
294 c_vector<double, SPACE_DIM> displacement = this->GetVectorFromAtoB(centroid, node_location);
295
296 c_vector<double, SPACE_DIM> rotated_location;
297 rotated_location[0] = cos_theta * displacement[0] - sin_theta * displacement[1];
298 rotated_location[1] = sin_theta * displacement[0] + cos_theta * displacement[1];
299
300 node_locations_original_order.push_back(rotated_location);
301 }
303 // Fill up a vector of identical points, and sort it so nodes are ordered in ascending x value
304 for (unsigned i = 0; i < node_locations_original_order.size(); i++)
305 {
306 ordered_locations.push_back(std::pair<unsigned, c_vector<double, SPACE_DIM> >(i, node_locations_original_order[i]));
308
309 std::sort(ordered_locations.begin(), ordered_locations.end(), CustomComparisonForSkewnessMeasure);
310
311 /*
312 * For each node, we must find every place where the axis (now rotated to be vertical) intersects the polygon:
313 *
314 * |
315 * __|______
316 * / | \
317 * / | \
318 * /____|___ |
319 * | | |
320 * _____|__| |
321 * \ | |
322 * \ | /
323 * \__|_____/
324 * |
325 * |
326 * ^
327 * For instance, the number of times the vertical intersects the polygon above is 4 and, for each node, we need to
328 * find all such intersections. We can do this by checking where the dot product of the the vector a with the unit
329 * x direction changes sign as we iterate over the original node locations, where a is the vector from the current
330 * node to the test node.
331 */
333 // For each node, we keep track of all the y-locations where the vertical through the node meets the polygon
334 std::vector<std::vector<double> > knots(num_nodes);
335
336 // Iterate over ordered locations from left to right
337 for (unsigned location = 0; location < num_nodes; location++)
338 {
339 // Get the two parts of the pair
340 unsigned this_idx = ordered_locations[location].first;
341 // this_location is the location of the node relative to centroid
342 c_vector<double, SPACE_DIM> this_location = ordered_locations[location].second;
343
344 // The y-coordinate of the current location is always a knot
345 // because we are passing the vertical through this node
346 knots[location].push_back(this_location[1]);
347
348 // To calculate all the intersection points, we need to iterate over every other location and see, sequentially,
349 // if the x-coordinate of location i+1 and i+2 crosses the x-coordinate of the current location.
350 // i.e. check whether each other edge crosses the vertical through this_location/current node
351 unsigned next_idx = (this_idx + 1) % num_nodes;
352 c_vector<double, SPACE_DIM> to_previous = node_locations_original_order[next_idx] - this_location;
353 c_vector<double, SPACE_DIM> to_next;
355 for (unsigned node_idx = this_idx + 2; node_idx < this_idx + num_nodes; node_idx++)
356 {
357 unsigned idx = node_idx % num_nodes;
358
359 to_next = node_locations_original_order[idx] - this_location;
360
361 // If the segment between to_previous and to_next intersects the vertical through this_location, the clause
362 // in the if statement below will be triggered
363 if (to_previous[0] * to_next[0] <= 0.0)
364 {
365 // Find how far between to_previous and to_next the point of intersection is
366 double interp = 0.5;
367 if (to_previous[0] - to_next[0] != 0.0)
368 {
369 interp = to_previous[0] / (to_previous[0] - to_next[0]);
370 }
372 assert(interp >= 0.0 && interp <= 1.0);
373
374 // Record the y-value of the intersection point
375 double new_intersection = this_location[1] + to_previous[1] + interp * (to_next[1] - to_previous[1]);
376 knots[location].push_back(new_intersection);
377 }
378
379 to_previous = to_next;
380 }
381
382 if (knots[location].size() > 2)
383 {
384 WARN_ONCE_ONLY("Axis intersects polygon more than 2 times (concavity) - check element is fairly convex.");
385 }
387
388 // For ease, construct a vector of the x-locations of all the nodes, in order
389 std::vector<double> ordered_x(num_nodes);
390 for (unsigned location = 0; location < num_nodes; location++)
392 ordered_x[location] = ordered_locations[location].second[0];
393 }
394
395 // Calculate the mass contributions at each x-location - this is the length of the intersection of the vertical
396 // through each location
397 std::vector<double> mass_contributions(num_nodes);
398 for (unsigned i = 0; i < num_nodes; i++)
399 {
400 std::sort(knots[i].begin(), knots[i].end());
402 switch (knots[i].size())
403 {
404 case 1:
405 mass_contributions[i] = 0.0;
406 break;
407
408 case 2:
409 mass_contributions[i] = knots[i][1] - knots[i][0];
410 break;
411
412 default:
413 mass_contributions[i] += knots[i][knots[i].size()-1] - knots[i][0];
414 }
415
416 // Normalise, so that these lengths define a pdf
417 mass_contributions[i] /= area_of_elem;
419
420 // Calculate moments. Because we just have a bunch of linear segments, we can integrate the pdf exactly
421 double e_x0 = 0.0;
422 double e_x1 = 0.0;
423 double e_x2 = 0.0;
424 double e_x3 = 0.0;
425
426 for (unsigned i = 1; i < num_nodes; i++)
427 {
428 double x0 = ordered_x[i - 1];
429 double x1 = ordered_x[i];
430
431 double fx0 = mass_contributions[i - 1];
432 double fx1 = mass_contributions[i];
433
434 // We need squared, cubed, ..., order 5 for each x
435 double x0_2 = x0 * x0;
436 double x0_3 = x0_2 * x0;
437 double x0_4 = x0_3 * x0;
438 double x0_5 = x0_4 * x0;
439
440 double x1_2 = x1 * x1;
441 double x1_3 = x1_2 * x1;
442 double x1_4 = x1_3 * x1;
443 double x1_5 = x1_4 * x1;
444
445 if (x1 - x0 > 0)
446 {
447 // Calculate y = mx + c for this section of the pdf
448 double m = (fx1 - fx0) / (x1 - x0);
449 double c = fx0 - m * x0;
450
451 e_x0 += m * (x1_2 - x0_2) / 2.0 + c * (x1 - x0);
452 e_x1 += m * (x1_3 - x0_3) / 3.0 + c * (x1_2 - x0_2) / 2.0;
453 e_x2 += m * (x1_4 - x0_4) / 4.0 + c * (x1_3 - x0_3) / 3.0;
454 e_x3 += m * (x1_5 - x0_5) / 5.0 + c * (x1_4 - x0_4) / 4.0;
455 }
456 }
457
458 // Check that we have correctly defined a pdf
459 if (fabs(e_x0 - 1.0) < 1e-6)
460 {
461 WARN_ONCE_ONLY("Mass distribution of element calculated incorrectly due to element concavity. Skewness may not be correct!");
463
464 // Calculate the standard deviation, and return the skewness
465 double sd = sqrt(e_x2 - e_x1 * e_x1);
466 return (e_x3 - 3.0 * e_x1 * sd * sd - e_x1 * e_x1 * e_x1) / (sd * sd * sd);
467 }
468 else
469 {
471 }
472}
473
474template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
476{
477 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_elem = this->GetElement(index);
478
479 // Get the location of node zero as a reference point
480 c_vector<double, SPACE_DIM> ref_point = p_elem->GetNode(0)->rGetLocation();
481
482 // Vector to represent the n-dimensional 'bottom left'-most node
483 c_vector<double, SPACE_DIM> bottom_left = zero_vector<double>(SPACE_DIM);
484
485 // Vector to represent the n-dimensional 'top right'-most node
486 c_vector<double, SPACE_DIM> top_right = zero_vector<double>(SPACE_DIM);
487
488 // Loop over all nodes in the element and update bottom_left and top_right, relative to node zero to account for periodicity
489 for (unsigned node_idx = 0; node_idx < p_elem->GetNumNodes(); node_idx++)
490 {
491 c_vector<double, SPACE_DIM> vec_to_node = this->GetVectorFromAtoB(ref_point, p_elem->GetNode(node_idx)->rGetLocation());
492
493 for (unsigned dim = 0; dim < SPACE_DIM; dim++)
494 {
495 if (vec_to_node[dim] < bottom_left[dim])
497 bottom_left[dim] = vec_to_node[dim];
498 }
499 else if (vec_to_node[dim] > top_right[dim])
500 {
501 top_right[dim] = vec_to_node[dim];
502 }
503 }
504 }
505
506 // Create Chaste points, rescaled by the location of node zero
507 ChastePoint<SPACE_DIM> min(bottom_left + ref_point);
508 ChastePoint<SPACE_DIM> max(top_right + ref_point);
509
510 return ChasteCuboid<SPACE_DIM>(min, max);
511}
512
513template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
516 this->mMeshChangesDuringSimulation = false;
517 Clear();
518}
519
520template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
525
526template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
528{
529 // Create a set of neighbouring node indices
530 std::set<unsigned> neighbouring_node_indices;
531
532 // Find the indices of the elements owned by this node
533 std::set<unsigned> containing_elem_indices = this->GetNode(nodeIndex)->rGetContainingElementIndices();
534
535 // Iterate over these elements
536 for (std::set<unsigned>::iterator elem_iter = containing_elem_indices.begin();
537 elem_iter != containing_elem_indices.end();
538 ++elem_iter)
539 {
540 // Find the local index of this node in this element
541 unsigned local_index = GetElement(*elem_iter)->GetNodeLocalIndex(nodeIndex);
542
543 // Find the global indices of the preceding and successive nodes in this element
544 unsigned num_nodes = GetElement(*elem_iter)->GetNumNodes();
545 unsigned previous_local_index = (local_index + num_nodes - 1) % num_nodes;
546 unsigned next_local_index = (local_index + 1) % num_nodes;
547
548 // Add the global indices of these two nodes to the set of neighbouring node indices
549 neighbouring_node_indices.insert(GetElement(*elem_iter)->GetNodeGlobalIndex(previous_local_index));
550 neighbouring_node_indices.insert(GetElement(*elem_iter)->GetNodeGlobalIndex(next_local_index));
551 }
552
553 return neighbouring_node_indices;
554}
556template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
558{
559 assert(index < this->mNodes.size());
560 return index;
561}
562
563template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
565{
566 assert(index < this->mElements.size());
567 return index;
568}
569
570template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
572{
573 return index;
574}
576template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
578{
579 // Delete elements
580 for (unsigned i = 0; i < mElements.size(); i++)
581 {
582 delete mElements[i];
583 }
584 mElements.clear();
585
586 // Delete laminas
587 for (auto lamina : mLaminas)
588 {
589 delete(lamina);
590 }
591 mLaminas.clear();
592
593 // Delete nodes
594 for (unsigned i = 0; i < this->mNodes.size(); i++)
596 delete this->mNodes[i];
597 }
598 this->mNodes.clear();
599
600 mBalancingFluidSources.clear();
601 mElementFluidSources.clear();
602}
603
604template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
606{
607 return mCharacteristicNodeSpacing;
608}
609
610template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
612{
613 return mCharacteristicNodeSpacing / (1.0 / double(mNumGridPtsX));
614}
615
616template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
617const std::vector<Node<SPACE_DIM>*>& ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::rGetNodes() const
618{
619 return this->mNodes;
620}
621
622template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
624{
625 return mNumGridPtsX;
626}
627
628template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
630{
631 return mNumGridPtsY;
632}
633
634template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
637 mNumGridPtsX = meshPointsX;
638 m2dVelocityGrids.resize(extents[2][mNumGridPtsX][mNumGridPtsY]);
639}
640
641template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
643{
644 mNumGridPtsY = meshPointsY;
645 m2dVelocityGrids.resize(extents[2][mNumGridPtsX][mNumGridPtsY]);
646}
647
648template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
650{
651 mNumGridPtsX = numGridPts;
652 mNumGridPtsY = numGridPts;
653 m2dVelocityGrids.resize(extents[2][mNumGridPtsX][mNumGridPtsY]);
655
656template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
658{
659 mCharacteristicNodeSpacing = nodeSpacing;
660}
661
662template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
663std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>>& ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::rGetElementFluidSources()
665 mElementFluidSources.clear();
666 for (const auto& element : mElements) {
667 if (element->GetFluidSource() != nullptr) {
668 mElementFluidSources.push_back(element->GetFluidSource());
670 }
671 return mElementFluidSources;
672}
673
674template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
675std::vector<std::shared_ptr<FluidSource<SPACE_DIM>>>& ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::rGetBalancingFluidSources()
676{
677 return mBalancingFluidSources;
678}
679
680template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
683 return m2dVelocityGrids;
684}
685
686template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
688{
689 return m2dVelocityGrids;
691
692template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
694 Node<SPACE_DIM>* pNewNode)
695{
696 pNewNode->SetIndex(this->mNodes.size());
697 this->mNodes.push_back(pNewNode);
698 return this->mNodes.size() - 1;
699}
700
701template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
702c_vector<double, SPACE_DIM> ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::GetVectorFromAtoB(const c_vector<double, SPACE_DIM>& rLocation1, const c_vector<double, SPACE_DIM>& rLocation2)
703{
704 // This code currently assumes the grid is precisely [0,1)x[0,1)
705 c_vector<double, SPACE_DIM> vector = rLocation2 - rLocation1;
706
707 /*
708 * Handle the periodic condition here: if the points are more
709 * than 0.5 apart in any direction, choose -(1.0-dist).
710 */
711 for (unsigned dim = 0; dim < SPACE_DIM; dim++)
712 {
713 if (fabs(vector[dim]) > 0.5)
714 {
715 vector[dim] = copysign(fabs(vector[dim]) - 1.0, -vector[dim]);
716 }
717 }
718
719 return vector;
720}
721
722template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
724{
725 this->mNodes[nodeIndex]->SetPoint(point);
726}
727
728template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
730{
731 return this->mNodes.size();
732}
734template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
736{
737 return mElements.size();
738}
739
740template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
742{
743 return mElements.size();
744}
745
746template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
748{
749 return mLaminas.size();
750}
752template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
754{
755 assert(index < mElements.size());
756 return mElements[index];
757}
758
759template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
761{
762 assert(index < mLaminas.size());
763 return mLaminas[index];
764}
765
766template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
768{
769 return mNeighbourDist;
770}
771
772template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
774{
775 mNeighbourDist = neighbourDist;
776}
777
778template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
780 [[maybe_unused]] unsigned index) // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
781{
782 // Only implemented in 2D
783 if constexpr (SPACE_DIM == 2)
784 {
785 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_element = GetElement(index);
786
787 unsigned num_nodes = p_element->GetNumNodes();
788 c_vector<double, SPACE_DIM> centroid = zero_vector<double>(SPACE_DIM);
789
790 double centroid_x = 0;
791 double centroid_y = 0;
792
793 // Note that we cannot use GetVolumeOfElement() below as it returns the absolute, rather than signed, area
794 double element_signed_area = 0.0;
795
796 // Map the first vertex to the origin and employ GetVectorFromAtoB() to allow for periodicity
797 c_vector<double, SPACE_DIM> first_node_location = p_element->GetNodeLocation(0);
798 c_vector<double, SPACE_DIM> pos_1 = zero_vector<double>(SPACE_DIM);
799
800 // Loop over vertices
801 for (unsigned local_index = 0; local_index < num_nodes; local_index++)
802 {
803 c_vector<double, SPACE_DIM> next_node_location = p_element->GetNodeLocation((local_index + 1) % num_nodes);
804 c_vector<double, SPACE_DIM> pos_2 = GetVectorFromAtoB(first_node_location, next_node_location);
805
806 double this_x = pos_1[0];
807 double this_y = pos_1[1];
808 double next_x = pos_2[0];
809 double next_y = pos_2[1];
810
811 double signed_area_term = this_x * next_y - this_y * next_x;
812
813 centroid_x += (this_x + next_x) * signed_area_term;
814 centroid_y += (this_y + next_y) * signed_area_term;
815 element_signed_area += 0.5 * signed_area_term;
816
817 pos_1 = pos_2;
818 }
819
820 assert(element_signed_area != 0.0);
821
822 // Finally, map back and employ GetVectorFromAtoB() to allow for periodicity
823 centroid = first_node_location;
824 centroid[0] += centroid_x / (6.0 * element_signed_area);
825 centroid[1] += centroid_y / (6.0 * element_signed_area);
826
827 centroid[0] = centroid[0] < 0 ? centroid[0] + 1.0 : fmod(centroid[0], 1.0);
828 centroid[1] = centroid[1] < 0 ? centroid[1] + 1.0 : fmod(centroid[1], 1.0);
829
830 return centroid;
831 }
832 else
833 {
835 }
836}
837
839template <>
842{
843 EXCEPTION("ImmersedBoundaryMesh not yet supported for the specified dimensions");
844}
845
847template <>
850{
851 EXCEPTION("ImmersedBoundaryMesh not yet supported for the specified dimensions");
852}
853
855template <>
858{
859 EXCEPTION("ImmersedBoundaryMesh not yet supported for the specified dimensions");
860}
861
863template <>
866{
867 EXCEPTION("ImmersedBoundaryMesh not yet supported for the specified dimensions");
868}
869
871template <>
874{
875 ImmersedBoundaryMeshReader<2, 2>& rIBMeshReader = dynamic_cast<ImmersedBoundaryMeshReader<2, 2>&>(rMeshReader);
876
877 assert(!rIBMeshReader.HasNodePermutation());
878
879 // Store numbers of nodes and elements
880 unsigned num_nodes = rIBMeshReader.GetNumNodes();
881 unsigned num_elements = rIBMeshReader.GetNumElements();
882 unsigned num_laminas = rIBMeshReader.GetNumLaminas();
883 this->mCharacteristicNodeSpacing = rIBMeshReader.GetCharacteristicNodeSpacing();
884
885 // Add nodes
886 rIBMeshReader.Reset();
887 mNodes.reserve(num_nodes);
888 std::vector<double> node_data;
889 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
890 {
891 node_data = rIBMeshReader.GetNextNode();
892 unsigned is_boundary_node = (bool)node_data[2];
893 node_data.pop_back();
894 this->mNodes.push_back(new Node<2>(node_idx, node_data, is_boundary_node));
895 }
896
897 // Add laminas
898 rIBMeshReader.Reset();
899 mLaminas.reserve(num_laminas);
900 for (unsigned lam_idx = 0; lam_idx < num_laminas; lam_idx++)
901 {
902 // Get the data for this element
904
905 // Get the nodes owned by this element
906 std::vector<Node<2>*> nodes;
907 unsigned num_nodes_in_lamina = lamina_data.NodeIndices.size();
908 for (unsigned node_idx = 0; node_idx < num_nodes_in_lamina; node_idx++)
909 {
910 assert(lamina_data.NodeIndices[node_idx] < this->mNodes.size());
911 nodes.push_back(this->mNodes[lamina_data.NodeIndices[node_idx]]);
912 }
913
914 // Use nodes and index to construct this element
915 ImmersedBoundaryElement<1, 2>* p_lamina = new ImmersedBoundaryElement<1, 2>(lam_idx, nodes);
916 mLaminas.push_back(p_lamina);
917
918 if (rIBMeshReader.GetNumLaminaAttributes() > 0)
919 {
920 assert(rIBMeshReader.GetNumLaminaAttributes() == 1);
921 unsigned attribute_value = lamina_data.AttributeValue;
922 p_lamina->SetAttribute(attribute_value);
923 }
924 }
925
926 // Add elements
927 rIBMeshReader.Reset();
928 mElements.reserve(num_elements);
929 for (unsigned elem_idx = 0; elem_idx < num_elements; elem_idx++)
930 {
931 // Get the data for this element
933
934 // Get the nodes owned by this element
935 std::vector<Node<2>*> nodes;
936 unsigned num_nodes_in_element = element_data.NodeIndices.size();
937 for (unsigned node_idx = 0; node_idx < num_nodes_in_element; node_idx++)
938 {
939 assert(element_data.NodeIndices[node_idx] < this->mNodes.size());
940 nodes.push_back(this->mNodes[element_data.NodeIndices[node_idx]]);
941 }
942
943 // Use nodes and index to construct this element
944 ImmersedBoundaryElement<2, 2>* p_element = new ImmersedBoundaryElement<2, 2>(elem_idx, nodes);
945 mElements.push_back(p_element);
946
947 if (rIBMeshReader.GetNumElementAttributes() > 0)
948 {
949 assert(rIBMeshReader.GetNumElementAttributes() == 1);
950 unsigned attribute_value = element_data.AttributeValue;
951 p_element->SetAttribute(attribute_value);
952 }
953 }
954
955 // Get grid dimensions from grid file and set up grids accordingly
956 this->mNumGridPtsX = rIBMeshReader.GetNumGridPtsX();
957 this->mNumGridPtsY = rIBMeshReader.GetNumGridPtsY();
958 m2dVelocityGrids.resize(extents[2][mNumGridPtsX][mNumGridPtsY]);
959
960 // Construct the velocity grids from mesh reader
961 for (unsigned dim = 0; dim < 2; dim++)
962 {
963 for (unsigned grid_row = 0; grid_row < mNumGridPtsY; grid_row++)
964 {
965 std::vector<double> next_row = rIBMeshReader.GetNextGridRow();
966 assert(next_row.size() == mNumGridPtsX);
967
968 for (unsigned i = 0; i < mNumGridPtsX; i++)
969 {
970 m2dVelocityGrids[dim][i][grid_row] = next_row[i];
971 }
972 }
973 }
974}
975
977template <>
980{
981 EXCEPTION("ImmersedBoundaryMesh not yet supported for the specified dimensions");
982}
983
984template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
986{
987 if constexpr (SPACE_DIM == 2)
988 {
989 // Get pointer to this element
990 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_element = GetElement(index);
991
992 double element_volume = 0.0;
993
994 // We map the first vertex to the origin and employ GetVectorFromAtoB() to allow for periodicity
995 c_vector<double, SPACE_DIM> first_node_location = p_element->GetNodeLocation(0);
996 c_vector<double, SPACE_DIM> pos_1 = zero_vector<double>(SPACE_DIM);
997
998 unsigned num_nodes = p_element->GetNumNodes();
999 for (unsigned local_index = 0; local_index < num_nodes; local_index++)
1000 {
1001 c_vector<double, SPACE_DIM> next_node_location = p_element->GetNodeLocation((local_index + 1) % num_nodes);
1002 c_vector<double, SPACE_DIM> pos_2 = GetVectorFromAtoB(first_node_location, next_node_location);
1003
1004 double this_x = pos_1[0];
1005 double this_y = pos_1[1];
1006 double next_x = pos_2[0];
1007 double next_y = pos_2[1];
1008
1009 element_volume += 0.5 * (this_x * next_y - next_x * this_y);
1010
1011 pos_1 = pos_2;
1012 }
1013
1014 // We take the absolute value just in case the nodes were really oriented clockwise
1015 return fabs(element_volume);
1016 }
1017 else
1018 {
1020 }
1021}
1022
1023template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1025{
1026 if constexpr (SPACE_DIM == 2)
1027 {
1028 // Get pointer to this element
1029 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_element = GetElement(index);
1030
1031 double surface_area = 0.0;
1032 unsigned num_nodes = p_element->GetNumNodes();
1033 unsigned this_node_index = p_element->GetNodeGlobalIndex(0);
1034 for (unsigned local_index = 0; local_index < num_nodes; local_index++)
1035 {
1036 unsigned next_node_index = p_element->GetNodeGlobalIndex((local_index + 1) % num_nodes);
1037 surface_area += this->GetDistanceBetweenNodes(this_node_index, next_node_index);
1038 this_node_index = next_node_index;
1039 }
1040
1041 return surface_area;
1042 }
1043 else
1044 {
1046 }
1047}
1048
1049template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1051{
1052 if constexpr (SPACE_DIM == 2)
1053 {
1054 UpdateNodeLocationsVoronoiDiagramIfOutOfDate();
1055 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_element = GetElement(elemIdx);
1056 double surface_area = 0.0;
1057
1058 // Sum contributions from each node in the element
1059 for (unsigned local_idx = 0; local_idx < p_element->GetNumNodes(); local_idx++)
1060 {
1061 const unsigned global_node_idx = p_element->GetNodeGlobalIndex(local_idx);
1062
1063 // Get the voronoi cell that corresponds to this node
1064 const unsigned voronoi_cell_id = mVoronoiCellIdsIndexedByNodeIndex[global_node_idx];
1065 const auto& voronoi_cell = mNodeLocationsVoronoiDiagram.cells()[voronoi_cell_id];
1066
1067 // Iterate over the edges of this voronoi cell
1068 auto p_edge = voronoi_cell.incident_edge();
1069 do
1070 {
1071 // The global node index corresponding to a voronoi cell is encoded in its 'color' variable
1072 const unsigned twin_node_idx = p_edge->twin()->cell()->color();
1073 const unsigned twin_elem_idx = *this->GetNode(twin_node_idx)->ContainingElementsBegin();
1074
1075 // Check if the nodes are in different elements
1076 if (elemIdx != twin_elem_idx)
1077 {
1078 surface_area += this->CalculateLengthOfVoronoiEdge(*p_edge);
1079 }
1080
1081 p_edge = p_edge->next();
1082 } while (p_edge != voronoi_cell.incident_edge());
1083 }
1084
1085 return surface_area;
1086 }
1087 else
1088 {
1090 }
1091}
1092
1093// Excluded from coverage as it is impossible to construct a 1 dimensional element currently
1094//LCOV_EXCL_START
1100template <>
1102{
1103 return 0.0;
1104}
1105//LCOV_EXCL_STOP
1106
1107template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1109{
1110 if (recalculate || (this->GetElement(index)->GetAverageNodeSpacing() == DOUBLE_UNSET))
1111 {
1112 double average_node_spacing = this->GetSurfaceAreaOfElement(index) / this->GetElement(index)->GetNumNodes();
1113 this->GetElement(index)->SetAverageNodeSpacing(average_node_spacing);
1114
1115 return average_node_spacing;
1116 }
1117 else
1118 {
1119 return this->GetElement(index)->GetAverageNodeSpacing();
1120 }
1121}
1122
1123template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1125{
1126 if (recalculate || (this->GetLamina(index)->GetAverageNodeSpacing() == DOUBLE_UNSET))
1127 {
1128 ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>* p_lam = this->GetLamina(index);
1129
1130 // Explicitly calculate the average node spacing
1131 double average_node_spacing = 0.0;
1132 for (unsigned node_it = 1; node_it < p_lam->GetNumNodes(); node_it++)
1133 {
1134 average_node_spacing += this->GetDistanceBetweenNodes(p_lam->GetNodeGlobalIndex(node_it),
1135 p_lam->GetNodeGlobalIndex(node_it - 1));
1136 }
1137
1138 average_node_spacing /= (p_lam->GetNumNodes() - 1);
1139
1140 // Set it for quick retrieval next time
1141 p_lam->SetAverageNodeSpacing(average_node_spacing);
1142 return average_node_spacing;
1143 }
1144 else
1145 {
1146 return this->GetLamina(index)->GetAverageNodeSpacing();
1147 }
1148}
1149
1150template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1152{
1153 return mElementDivisionSpacing;
1154}
1155
1156template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1158{
1159 mElementDivisionSpacing = elementDivisionSpacing;
1160}
1161
1163// 2D-specific methods //
1165
1166template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1168{
1169 if constexpr (SPACE_DIM == 2)
1170 {
1171 // Define helper variables
1172 ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>* p_element = GetElement(index);
1173 unsigned num_nodes = p_element->GetNumNodes();
1174 c_vector<double, 3> moments = zero_vector<double>(3);
1175
1176 // Since we compute I_xx, I_yy and I_xy about the centroid, we must shift each vertex accordingly
1177 c_vector<double, SPACE_DIM> centroid = GetCentroidOfElement(index);
1178
1179 c_vector<double, SPACE_DIM> this_node_location = p_element->GetNodeLocation(0);
1180 c_vector<double, SPACE_DIM> pos_1 = this->GetVectorFromAtoB(centroid, this_node_location);
1181
1182 for (unsigned local_index = 0; local_index < num_nodes; local_index++)
1183 {
1184 unsigned next_index = (local_index + 1) % num_nodes;
1185 c_vector<double, SPACE_DIM> next_node_location = p_element->GetNodeLocation(next_index);
1186 c_vector<double, SPACE_DIM> pos_2 = this->GetVectorFromAtoB(centroid, next_node_location);
1187
1188 double signed_area_term = pos_1(0) * pos_2(1) - pos_2(0) * pos_1(1);
1189 // Ixx
1190 moments(0) += (pos_1(1) * pos_1(1) + pos_1(1) * pos_2(1) + pos_2(1) * pos_2(1)) * signed_area_term;
1191
1192 // Iyy
1193 moments(1) += (pos_1(0) * pos_1(0) + pos_1(0) * pos_2(0) + pos_2(0) * pos_2(0)) * signed_area_term;
1194
1195 // Ixy
1196 moments(2) += (pos_1(0) * pos_2(1) + 2 * pos_1(0) * pos_1(1) + 2 * pos_2(0) * pos_2(1) + pos_2(0) * pos_1(1)) * signed_area_term;
1197
1198 pos_1 = pos_2;
1199 }
1200
1201 moments(0) /= 12;
1202 moments(1) /= 12;
1203 moments(2) /= 24;
1204
1205 /*
1206 * If the nodes owned by the element were supplied in a clockwise rather
1207 * than anticlockwise manner, or if this arose as a result of enforcing
1208 * periodicity, then our computed quantities will be the wrong sign, so
1209 * we need to fix this.
1210 */
1211 if (moments(0) < 0.0)
1212 {
1213 moments(0) = -moments(0);
1214 moments(1) = -moments(1);
1215 moments(2) = -moments(2);
1216 }
1217 return moments;
1218 }
1219 else
1220 {
1222 }
1223}
1224
1225template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1227{
1228 if constexpr (SPACE_DIM == 2)
1229 {
1230 c_vector<double, SPACE_DIM> short_axis = zero_vector<double>(SPACE_DIM);
1231
1232 // Calculate the moments of the element about its centroid (recall that I_xx and I_yy must be non-negative)
1233 c_vector<double, 3> moments = CalculateMomentsOfElement(index);
1234
1235 // Normalise the moments vector to remove problem of a very small discriminant (see #2874)
1236 moments /= norm_2(moments);
1237
1238 // If the principal moments are equal...
1239 double discriminant = (moments(0) - moments(1)) * (moments(0) - moments(1)) + 4.0 * moments(2) * moments(2);
1240 if (fabs(discriminant) < DBL_EPSILON)
1241 {
1242 // ...then every axis through the centroid is a principal axis, so return a random unit vector
1243 short_axis(0) = RandomNumberGenerator::Instance()->ranf();
1244 short_axis(1) = sqrt(1.0 - short_axis(0) * short_axis(0));
1245 }
1246 else
1247 {
1248 // If the product of inertia is zero, then the coordinate axes are the principal axes
1249 if (fabs(moments(2)) < DBL_EPSILON)
1250 {
1251 if (moments(0) < moments(1))
1252 {
1253 short_axis(0) = 0.0;
1254 short_axis(1) = 1.0;
1255 }
1256 else
1257 {
1258 short_axis(0) = 1.0;
1259 short_axis(1) = 0.0;
1260 }
1261 }
1262 else
1263 {
1264 // Otherwise we find the eigenvector of the inertia matrix corresponding to the largest eigenvalue
1265 double lambda = 0.5 * (moments(0) + moments(1) + sqrt(discriminant));
1266
1267 short_axis(0) = 1.0;
1268 short_axis(1) = (moments(0) - lambda) / moments(2);
1269
1270 // Normalise the short axis before returning it
1271 short_axis /= norm_2(short_axis);
1272 }
1273 }
1274
1275 return short_axis;
1276 }
1277 else
1278 {
1280 }
1281}
1282
1283template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1285 c_vector<double, SPACE_DIM> axisOfDivision,
1286 bool placeOriginalElementBelow)
1287{
1288 if constexpr (SPACE_DIM == 2 && ELEMENT_DIM == 2)
1289 {
1290 // Get the centroid of the element
1291 c_vector<double, SPACE_DIM> centroid = this->GetCentroidOfElement(pElement->GetIndex());
1292
1293 // Create a vector perpendicular to the axis of division
1294 c_vector<double, SPACE_DIM> perp_axis;
1295 perp_axis(0) = -axisOfDivision(1);
1296 perp_axis(1) = axisOfDivision(0);
1297
1298 /*
1299 * Find which edges the axis of division crosses by finding any node
1300 * that lies on the opposite side of the axis of division to its next
1301 * neighbour.
1302 */
1303
1304 unsigned num_nodes = pElement->GetNumNodes();
1305 std::vector<unsigned> intersecting_nodes;
1306 bool is_current_node_on_left = (inner_prod(this->GetVectorFromAtoB(pElement->GetNodeLocation(0), centroid), perp_axis) >= 0);
1307 for (unsigned i = 0; i < num_nodes; i++)
1308 {
1309 bool is_next_node_on_left = (inner_prod(this->GetVectorFromAtoB(pElement->GetNodeLocation((i + 1) % num_nodes), centroid), perp_axis) >= 0);
1310 if (is_current_node_on_left != is_next_node_on_left)
1311 {
1312 intersecting_nodes.push_back(i);
1313 }
1314 is_current_node_on_left = is_next_node_on_left;
1315 }
1316
1317 // If the axis of division does not cross two edges then we cannot proceed
1318 if (intersecting_nodes.size() != 2)
1319 {
1320 EXCEPTION("Cannot proceed with element division: the given axis of division does not cross two edges of the element"); // LCOV_EXCL_LINE
1321 }
1322
1323 // Now call DivideElement() to divide the element using the nodes found above
1324 //unsigned new_element_index = 0;
1325 unsigned new_element_index = DivideElement(pElement,
1326 intersecting_nodes[0],
1327 intersecting_nodes[1],
1328 centroid,
1329 axisOfDivision);
1330
1331 return new_element_index;
1332 }
1333 else
1334 {
1336 }
1337}
1338
1339template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1341 bool placeOriginalElementBelow)
1342{
1343 if constexpr (SPACE_DIM == 2 && ELEMENT_DIM == 2)
1344 {
1345 c_vector<double, SPACE_DIM> short_axis = this->GetShortAxisOfElement(pElement->GetIndex());
1346 unsigned new_element_index = DivideElementAlongGivenAxis(pElement, short_axis, placeOriginalElementBelow);
1347 return new_element_index;
1348 }
1349 else
1350 {
1352 }
1353}
1354
1355template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1357 unsigned nodeAIndex,
1358 unsigned nodeBIndex,
1359 c_vector<double, SPACE_DIM> centroid,
1360 c_vector<double, SPACE_DIM> axisOfDivision)
1361{
1362 if constexpr (SPACE_DIM == 2 && ELEMENT_DIM == 2)
1363 {
1364 if (mElementDivisionSpacing == DOUBLE_UNSET)
1365 {
1366 EXCEPTION("The value of mElementDivisionSpacing has not been set.");
1367 }
1368
1369 /*
1370 * Method outline:
1371 *
1372 * Each element needs to end up with the same number of nodes as the original element, and those nodes will be
1373 * equally spaced around the outline of each of the two daughter elements.
1374 *
1375 * The two elements need to be divided by a distance of mElementDivisionSpacing, where the distance is measured
1376 * perpendicular to the axis of division.
1377 *
1378 * To achieve this, we find four 'corner' locations, each of which has a perpendicular distance from the axis of
1379 * half the required spacing, and are found by using the locations from the existing element as a stencil.
1380 */
1381
1382 double half_spacing = 0.5 * mElementDivisionSpacing;
1383
1384 // Get unit vectors in the direction of the division axis, and the perpendicular
1385 c_vector<double, SPACE_DIM> unit_axis = axisOfDivision / norm_2(axisOfDivision);
1386 c_vector<double, SPACE_DIM> unit_perp;
1387 unit_perp[0] = -unit_axis[1];
1388 unit_perp[1] = unit_axis[0];
1389
1390 unsigned num_nodes = pElement->GetNumNodes();
1391
1392 /*
1393 * We first identify the start and end indices of the nodes which will form the location stencil for each daughter
1394 * cell. Our starting point is the node indices already identified.
1395 *
1396 * In order to ensure the resulting gap between the elements is the correct size, we remove as many nodes as
1397 * necessary until the perpendicular distance between the centroid and the node is at least half the required
1398 * spacing.
1399 *
1400 * Finally, we move the relevant node to be exactly half the required spacing.
1401 */
1402 unsigned start_a = (nodeAIndex + 1) % num_nodes;
1403 unsigned end_a = nodeBIndex;
1404
1405 unsigned start_b = (nodeBIndex + 1) % num_nodes;
1406 unsigned end_b = nodeAIndex;
1407
1408 // Find correct start_a
1409 bool no_node_satisfied_condition_1 = true;
1410 for (unsigned i = start_a; i != end_a;)
1411 {
1412 c_vector<double, SPACE_DIM> centroid_to_i = this->GetVectorFromAtoB(centroid, pElement->GetNode(i)->rGetLocation());
1413 double perpendicular_dist = inner_prod(centroid_to_i, unit_perp);
1414
1415 if (fabs(perpendicular_dist) >= half_spacing)
1416 {
1417 no_node_satisfied_condition_1 = false;
1418 start_a = i;
1419
1420 // Calculate position so it's exactly 0.5 * elem_spacing perpendicular distance from the centroid
1421 c_vector<double, SPACE_DIM> new_location = pElement->GetNode(i)->rGetLocation();
1422 new_location -= unit_perp * copysign(fabs(perpendicular_dist) - half_spacing, perpendicular_dist);
1423
1424 pElement->GetNode(i)->SetPoint(ChastePoint<SPACE_DIM>(new_location));
1425 break;
1426 }
1427
1428 // Go to the next node
1429 i = (i + 1) % num_nodes;
1430 }
1431
1432 // Find correct end_a
1433 bool no_node_satisfied_condition_2 = true;
1434 for (unsigned i = end_a; i != start_a;)
1435 {
1436 c_vector<double, SPACE_DIM> centroid_to_i = this->GetVectorFromAtoB(centroid, pElement->GetNode(i)->rGetLocation());
1437 double perpendicular_dist = inner_prod(centroid_to_i, unit_perp);
1438
1439 if (fabs(perpendicular_dist) >= half_spacing)
1440 {
1441 no_node_satisfied_condition_2 = false;
1442 end_a = i;
1443
1444 // Calculate position so it's exactly 0.5 * elem_spacing perpendicular distance from the centroid
1445 c_vector<double, SPACE_DIM> new_location = pElement->GetNode(i)->rGetLocation();
1446 new_location -= unit_perp * copysign(fabs(perpendicular_dist) - half_spacing, perpendicular_dist);
1447
1448 pElement->GetNode(i)->SetPoint(ChastePoint<SPACE_DIM>(new_location));
1449 break;
1450 }
1451
1452 // Go to the previous node
1453 i = (i + num_nodes - 1) % num_nodes; // LCOV_EXCL_LINE
1454 }
1455
1456 // Find correct start_b
1457 bool no_node_satisfied_condition_3 = true;
1458 for (unsigned i = start_b; i != end_b;)
1459 {
1460 c_vector<double, SPACE_DIM> centroid_to_i = this->GetVectorFromAtoB(centroid, pElement->GetNode(i)->rGetLocation());
1461 double perpendicular_dist = inner_prod(centroid_to_i, unit_perp);
1462
1463 if (fabs(perpendicular_dist) >= half_spacing)
1464 {
1465 no_node_satisfied_condition_3 = false;
1466 start_b = i;
1467
1468 // Calculate position so it's exactly 0.5 * elem_spacing perpendicular distance from the centroid
1469 c_vector<double, SPACE_DIM> new_location = pElement->GetNode(i)->rGetLocation();
1470 new_location -= unit_perp * copysign(fabs(perpendicular_dist) - half_spacing, perpendicular_dist);
1471
1472 pElement->GetNode(i)->SetPoint(ChastePoint<SPACE_DIM>(new_location));
1473 break;
1474 }
1475
1476 // Go to the next node
1477 i = (i + 1) % num_nodes;
1478 }
1479
1480 // Find correct end_b
1481 bool no_node_satisfied_condition_4 = true;
1482 for (unsigned i = end_b; i != start_b;)
1483 {
1484 c_vector<double, SPACE_DIM> centroid_to_i = this->GetVectorFromAtoB(centroid, pElement->GetNode(i)->rGetLocation());
1485 double perpendicular_dist = inner_prod(centroid_to_i, unit_perp);
1486
1487 if (fabs(perpendicular_dist) >= half_spacing)
1488 {
1489 no_node_satisfied_condition_4 = false;
1490 end_b = i;
1491
1492 // Calculate position so it's exactly 0.5 * elem_spacing perpendicular distance from the centroid
1493 c_vector<double, SPACE_DIM> new_location = pElement->GetNode(i)->rGetLocation();
1494 new_location -= unit_perp * copysign(fabs(perpendicular_dist) - half_spacing, perpendicular_dist);
1495
1496 pElement->GetNode(i)->SetPoint(ChastePoint<SPACE_DIM>(new_location));
1497 break;
1498 }
1499
1500 // Go to the previous node
1501 i = (i + num_nodes - 1) % num_nodes;
1502 }
1503
1504 if (no_node_satisfied_condition_1 || no_node_satisfied_condition_2 || no_node_satisfied_condition_3 || no_node_satisfied_condition_4)
1505 {
1506 EXCEPTION("Could not space elements far enough apart during cell division. Cannot currently handle this case");
1507 }
1508
1509 /*
1510 * Create location stencils for each of the daughter cells
1511 */
1512 std::vector<c_vector<double, SPACE_DIM> > daughter_a_location_stencil;
1513 for (unsigned node_idx = start_a; node_idx != (end_a + 1) % num_nodes;)
1514 {
1515 daughter_a_location_stencil.push_back(c_vector<double, SPACE_DIM>(pElement->GetNode(node_idx)->rGetLocation()));
1516
1517 // Go to next node
1518 node_idx = (node_idx + 1) % num_nodes;
1519 }
1520
1521 std::vector<c_vector<double, SPACE_DIM> > daughter_b_location_stencil;
1522 for (unsigned node_idx = start_b; node_idx != (end_b + 1) % num_nodes;)
1523 {
1524 daughter_b_location_stencil.push_back(c_vector<double, SPACE_DIM>(pElement->GetNode(node_idx)->rGetLocation()));
1525
1526 // Go to next node
1527 node_idx = (node_idx + 1) % num_nodes;
1528 }
1529
1530 assert(daughter_a_location_stencil.size() > 1);
1531 assert(daughter_b_location_stencil.size() > 1);
1532
1533 // To help calculating cumulative distances, add the first location on to the end
1534 daughter_a_location_stencil.push_back(daughter_a_location_stencil[0]);
1535 daughter_b_location_stencil.push_back(daughter_b_location_stencil[0]);
1536
1537 // Calculate the cumulative distances around the stencils
1538 std::vector<double> cumulative_distances_a;
1539 std::vector<double> cumulative_distances_b;
1540 cumulative_distances_a.push_back(0.0);
1541 cumulative_distances_b.push_back(0.0);
1542 for (unsigned loc_idx = 1; loc_idx < daughter_a_location_stencil.size(); loc_idx++)
1543 {
1544 cumulative_distances_a.push_back(cumulative_distances_a.back() + norm_2(this->GetVectorFromAtoB(daughter_a_location_stencil[loc_idx - 1], daughter_a_location_stencil[loc_idx])));
1545 }
1546 for (unsigned loc_idx = 1; loc_idx < daughter_b_location_stencil.size(); loc_idx++)
1547 {
1548 cumulative_distances_b.push_back(cumulative_distances_b.back() + norm_2(this->GetVectorFromAtoB(daughter_b_location_stencil[loc_idx - 1], daughter_b_location_stencil[loc_idx])));
1549 }
1550
1551 // Find the target node spacing for each of the daughter elements
1552 double target_spacing_a = cumulative_distances_a.back() / (double)num_nodes;
1553 double target_spacing_b = cumulative_distances_b.back() / (double)num_nodes;
1554
1555 // Move the existing nodes into position to become daughter-A nodes
1556 unsigned last_idx_used = 0;
1557 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
1558 {
1559 double location_along_arc = (double)node_idx * target_spacing_a;
1560
1561 while (location_along_arc > cumulative_distances_a[last_idx_used + 1])
1562 {
1563 last_idx_used++;
1564 }
1565
1566 // Interpolant is the extra distance past the last index used divided by the length of the next line segment
1567 double interpolant = (location_along_arc - cumulative_distances_a[last_idx_used]) / (cumulative_distances_a[last_idx_used + 1] - cumulative_distances_a[last_idx_used]);
1568
1569 c_vector<double, SPACE_DIM> this_to_next = this->GetVectorFromAtoB(daughter_a_location_stencil[last_idx_used],
1570 daughter_a_location_stencil[last_idx_used + 1]);
1571
1572 c_vector<double, SPACE_DIM> new_location_a = daughter_a_location_stencil[last_idx_used] + interpolant * this_to_next;
1573
1574 pElement->GetNode(node_idx)->SetPoint(ChastePoint<SPACE_DIM>(new_location_a));
1575 }
1576
1577 // Create new nodes at positions around the daughter-B stencil
1578 last_idx_used = 0;
1579 std::vector<Node<SPACE_DIM>*> new_nodes_vec;
1580 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
1581 {
1582 double location_along_arc = (double)node_idx * target_spacing_b;
1583
1584 while (location_along_arc > cumulative_distances_b[last_idx_used + 1])
1585 {
1586 last_idx_used++;
1587 }
1588
1589 // Interpolant is the extra distance past the last index used divided by the length of the next line segment
1590 double interpolant = (location_along_arc - cumulative_distances_b[last_idx_used]) / (cumulative_distances_b[last_idx_used + 1] - cumulative_distances_b[last_idx_used]);
1591
1592 c_vector<double, SPACE_DIM> this_to_next = this->GetVectorFromAtoB(daughter_b_location_stencil[last_idx_used],
1593 daughter_b_location_stencil[last_idx_used + 1]);
1594
1595 c_vector<double, SPACE_DIM> new_location_b = daughter_b_location_stencil[last_idx_used] + interpolant * this_to_next;
1596
1597 unsigned new_node_idx = this->mNodes.size();
1598 this->mNodes.push_back(new Node<SPACE_DIM>(new_node_idx, new_location_b, true));
1599 new_nodes_vec.push_back(this->mNodes.back());
1600 }
1601
1602 // Copy node attributes
1603 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
1604 {
1605 new_nodes_vec[node_idx]->SetRegion(pElement->GetNode(node_idx)->GetRegion());
1606
1607 for (unsigned node_attribute = 0; node_attribute < pElement->GetNode(node_idx)->GetNumNodeAttributes(); node_attribute++)
1608 {
1609 new_nodes_vec[node_idx]->AddNodeAttribute(pElement->GetNode(node_idx)->rGetNodeAttributes()[node_attribute]);
1610 }
1611 }
1612
1613 // Create the new element
1614 unsigned new_elem_idx = this->mElements.size();
1615 this->mElements.push_back(new ImmersedBoundaryElement<ELEMENT_DIM, SPACE_DIM>(new_elem_idx, new_nodes_vec));
1616 this->mElements.back()->RegisterWithNodes();
1617
1618 // Copy any element attributes
1619 for (unsigned elem_attribute = 0; elem_attribute < pElement->GetNumElementAttributes(); elem_attribute++)
1620 {
1621 this->mElements.back()->AddElementAttribute(pElement->rGetElementAttributes()[elem_attribute]);
1622 }
1623
1624 // Add the necessary corners to keep consistency with the other daughter element
1625 for (unsigned corner = 0; corner < pElement->rGetCornerNodes().size(); corner++)
1626 {
1627 this->mElements.back()->rGetCornerNodes().push_back(pElement->rGetCornerNodes()[corner]);
1628 }
1629
1630 // Update fluid source location for the existing element
1631 pElement->GetFluidSource()->rGetModifiableLocation() = this->GetCentroidOfElement(pElement->GetIndex());
1632
1633 // Add a fluid source for the new element
1634 c_vector<double, SPACE_DIM> new_centroid = this->GetCentroidOfElement(new_elem_idx);
1635 mElementFluidSources.push_back(std::make_shared<FluidSource<SPACE_DIM>>(new_elem_idx, new_centroid));
1636
1637 // Set source parameters
1638 mElementFluidSources.back()->SetAssociatedElementIndex(new_elem_idx);
1639 mElementFluidSources.back()->SetStrength(0.0);
1640
1641 // Associate source with element
1642 mElements[new_elem_idx]->SetFluidSource(mElementFluidSources.back());
1643
1644 return new_elem_idx;
1645 }
1646 else
1647 {
1649 }
1650}
1651
1652template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1654{
1655 // Iterate over elements and remesh each one
1656 for (auto p_elem : mElements)
1657 {
1658 ReMeshElement(p_elem, randomOrder);
1659 }
1660
1661 // Iterate over laminas and remesh each one
1662 for (auto p_lam : mLaminas)
1663 {
1664 ReMeshLamina(p_lam, randomOrder);
1665 }
1666
1667 // Reposition fluid sources to the centroid of cells
1668 for (auto& p_source : mElementFluidSources)
1669 {
1670 p_source->rGetModifiableLocation() = this->GetCentroidOfElement(p_source->GetAssociatedElementIndex());
1671 }
1672}
1673
1674template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1676 bool randomOrder)
1677{
1678 if constexpr (SPACE_DIM == 2)
1679 {
1680 const unsigned num_nodes = pElement->GetNumNodes();
1681
1682 // Start at a random location in the vector of element nodes if requested
1683 const unsigned start_idx = randomOrder ? RandomNumberGenerator::Instance()->randMod(num_nodes) : 0u;
1684
1685 // Straighten out locations to a contiguous polygon rather than wrapped around the domain due to periodicity
1686 std::vector<c_vector<double, SPACE_DIM>> locations_straightened;
1687 locations_straightened.reserve(num_nodes);
1688
1689 locations_straightened.emplace_back(pElement->GetNodeLocation(start_idx));
1690
1691 for (unsigned node_idx = 1; node_idx < num_nodes; ++node_idx)
1692 {
1693 const unsigned prev_idx = AdvanceMod(start_idx, node_idx - 1, num_nodes);
1694 const unsigned this_idx = AdvanceMod(start_idx, node_idx, num_nodes);
1695
1696 const c_vector<double, SPACE_DIM>& r_last_location_added = locations_straightened.back();
1697
1698 const c_vector<double, SPACE_DIM>& r_prev_location = pElement->GetNodeLocation(prev_idx);
1699 const c_vector<double, SPACE_DIM>& r_this_location = pElement->GetNodeLocation(this_idx);
1700
1701 locations_straightened.emplace_back(r_last_location_added +
1702 this->GetVectorFromAtoB(r_prev_location, r_this_location));
1703 }
1704
1705 assert(locations_straightened.size() == num_nodes);
1706
1707 const bool closed_path = true;
1708 const bool permute_order = false;
1709 const std::size_t num_pts_to_place = num_nodes;
1710
1711 std::vector<c_vector<double, SPACE_DIM>> evenly_spaced_locations = EvenlySpaceAlongPath(
1712 locations_straightened,
1713 closed_path,
1714 permute_order,
1715 num_pts_to_place
1716 );
1717
1718 assert(evenly_spaced_locations.size() == num_nodes);
1719
1720 // Conform all locations to geometry
1721 for (c_vector<double, SPACE_DIM>& r_loc : evenly_spaced_locations)
1722 {
1723 ConformToGeometry(r_loc);
1724 }
1725
1726 // Update the node locations
1727 for (unsigned node_idx = 0; node_idx < num_nodes; ++node_idx)
1728 {
1729 const unsigned this_idx = AdvanceMod(node_idx, start_idx, num_nodes);
1730 pElement->GetNode(this_idx)->rGetModifiableLocation() = evenly_spaced_locations[node_idx];
1731 }
1732 }
1733 else
1734 {
1736 }
1737}
1738
1739template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1741 bool randomOrder)
1742{
1743 if constexpr (SPACE_DIM == 2)
1744 {
1745 const unsigned num_nodes = pLamina->GetNumNodes();
1746
1747 // Start at a random location in the vector of element nodes
1748 const unsigned start_idx = randomOrder ? RandomNumberGenerator::Instance()->randMod(num_nodes) : 0u;
1749
1750 // Straighten out locations to a contiguous line rather than wrapped around the domain due to periodicity
1751 std::vector<c_vector<double, SPACE_DIM>> locations_straightened;
1752 locations_straightened.reserve(1 + num_nodes);
1753
1754 locations_straightened.emplace_back(pLamina->GetNodeLocation(start_idx));
1755
1756 // We go to 1 + num_nodes so that we add on a node in a location congruent to the first location added
1757 for (unsigned node_idx = 1; node_idx < 1 + num_nodes; ++node_idx)
1758 {
1759 const unsigned prev_idx = AdvanceMod(start_idx, node_idx - 1, num_nodes);
1760 const unsigned this_idx = AdvanceMod(start_idx, node_idx, num_nodes);
1761
1762 const c_vector<double, SPACE_DIM>& r_last_location_added = locations_straightened.back();
1763
1764 const c_vector<double, SPACE_DIM>& r_prev_location = pLamina->GetNodeLocation(prev_idx);
1765 const c_vector<double, SPACE_DIM>& r_this_location = pLamina->GetNodeLocation(this_idx);
1766
1767 locations_straightened.emplace_back(r_last_location_added +
1768 this->GetVectorFromAtoB(r_prev_location, r_this_location));
1769 }
1770
1771 assert(locations_straightened.size() == 1 + num_nodes);
1772
1773 const bool closed_path = false;
1774 const bool permute_order = false;
1775 const std::size_t num_pts_to_place = 1 + num_nodes;
1776
1777 std::vector<c_vector<double, SPACE_DIM>> evenly_spaced_locations = EvenlySpaceAlongPath(
1778 locations_straightened,
1779 closed_path,
1780 permute_order,
1781 num_pts_to_place
1782 );
1783
1784 assert(evenly_spaced_locations.size() == 1 + num_nodes);
1785
1786 // Conform all locations to geometry
1787 for (c_vector<double, SPACE_DIM>& r_loc : evenly_spaced_locations)
1788 {
1789 ConformToGeometry(r_loc);
1790 }
1791
1792 // Update the node locations, ignoring the very last location that was added to make the path spacing even
1793 for (unsigned node_idx = 0; node_idx < num_nodes; ++node_idx)
1794 {
1795 const unsigned this_idx = AdvanceMod(start_idx, node_idx, num_nodes);
1796 pLamina->GetNode(this_idx)->rGetModifiableLocation() = evenly_spaced_locations[node_idx];
1797 }
1798 }
1799 else
1800 {
1802 }
1803}
1804
1805template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1807{
1808 for (unsigned dim = 0; dim < SPACE_DIM; ++dim)
1809 {
1810 assert(rLocation[dim] >= -2.0); // It's expected that the location is near the domain. This method is
1811 assert(rLocation[dim] < 3.0); // inefficient if the location is far away.
1812
1813 while (rLocation[dim] < 0.0)
1814 {
1815 rLocation[dim] += 1.0;
1816 }
1817
1818 while (rLocation[dim] >= 1.0)
1819 {
1820 rLocation[dim] -= 1.0;
1821 }
1822 }
1823}
1824
1825template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1827 Node<SPACE_DIM>* pNodeB)
1828{
1829 // If neither are lamina nodes, we can just check equality of first containing element indices
1830 if (pNodeA->GetRegion() != LAMINA_REGION && pNodeB->GetRegion() != LAMINA_REGION)
1831 {
1832 return *(pNodeA->ContainingElementsBegin()) != *(pNodeB->ContainingElementsBegin());
1833 }
1834 else // either one or both nodes is in lamina; assume that two laminas never interact
1835 {
1836 return true;
1837 }
1838}
1839
1840template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1842{
1843 if (!mLaminas.empty())
1844 {
1845 EXCEPTION("This method does not yet work in the presence of laminas");
1846 }
1847
1848 /*
1849 * This method uses geometric information from a voronoi diagram of every node. The voronoi cell of an element
1850 * (the union of voronoi cells of nodes in the element) provides a precise boundary between elements on which the
1851 * concept of 'neighbourhood' is well-defined.
1852 *
1853 * We first update the voronoi diagram, if it is out of date.
1854 */
1855 UpdateNodeLocationsVoronoiDiagramIfOutOfDate();
1856
1857 std::set<unsigned> neighbouring_element_indices;
1858
1859 for (unsigned node_local_idx = 0; node_local_idx < this->GetElement(elemIdx)->GetNumNodes(); ++node_local_idx)
1860 {
1861 const unsigned node_global_idx = this->GetElement(elemIdx)->GetNodeGlobalIndex(node_local_idx);
1862 const unsigned voronoi_cell_id = mVoronoiCellIdsIndexedByNodeIndex[node_global_idx];
1863
1864 /*
1865 * Iterate over the edges of the voronoi cell corresponding to the current node. Each primary edge has a twin
1866 * in a voronoi cell corresponding to a different node. The element containing that node (which may be the
1867 * current element under consideration) is added to the set of neighbours precisely if the distance between
1868 * nodes is less than mNeighbourDist.
1869 */
1870 const auto voronoi_cell = mNodeLocationsVoronoiDiagram.cells()[voronoi_cell_id];
1871 auto p_edge = voronoi_cell.incident_edge();
1872
1873 do
1874 {
1875 // The global node index corresponding to a voronoi cell cell is encoded in its 'color' variable
1876 const unsigned twin_node_idx = p_edge->twin()->cell()->color();
1877 const unsigned twin_elem_idx = *this->GetNode(twin_node_idx)->ContainingElementsBegin();
1878
1879 // Only bother to check the node distances if the nodes are in different elements
1880 if (twin_elem_idx != elemIdx)
1881 {
1882 if (this->GetDistanceBetweenNodes(node_global_idx, twin_node_idx) < mNeighbourDist)
1883 {
1884 neighbouring_element_indices.insert(twin_elem_idx);
1885 }
1886 }
1887 p_edge = p_edge->next();
1888 } while (p_edge != voronoi_cell.incident_edge());
1889 }
1890
1891 return neighbouring_element_indices;
1892} // LCOV_EXCL_LINE
1893
1894template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1896{
1897 if constexpr (SPACE_DIM == 2)
1898 {
1899 std::array<unsigned, 13> polygon_dist = {{0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}};
1900
1901 for (auto elem_it = this->GetElementIteratorBegin(); elem_it != this->GetElementIteratorEnd(); ++elem_it)
1902 {
1903 if (!elem_it->IsElementOnBoundary())
1904 {
1905 // Accumulate all 12+ sided shapes
1906 unsigned num_neighbours = std::min<unsigned>(12u, GetNeighbouringElementIndices(elem_it->GetIndex()).size());
1907 polygon_dist[num_neighbours]++;
1908 }
1909 }
1910
1911 return polygon_dist;
1912 }
1913 else
1914 {
1916 }
1917}
1918
1919template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1920double ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::CalculateLengthOfVoronoiEdge(const boost::polygon::voronoi_diagram<double>::edge_type& rEdge)
1921{
1922 assert(rEdge.is_finite());
1923
1924 const double d_x = rEdge.vertex1()->x() - rEdge.vertex0()->x();
1925 const double d_y = rEdge.vertex1()->y() - rEdge.vertex0()->y();
1926
1927 return ScaleDistanceDownFromVoronoi(std::sqrt(d_x * d_x + d_y * d_y));
1928}
1929
1930template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1932{
1933 if (this->mNodes.begin() == this->mNodes.end())
1934 {
1935 return UINT_MAX;
1936 }
1937 else
1938 {
1939 const auto max_idx_it = std::max_element(this->mNodes.begin(), this->mNodes.end(),
1940 [](Node<SPACE_DIM>* const a, Node<SPACE_DIM>* const b)
1941 {
1942 return a->GetIndex() < b->GetIndex();
1943 });
1944
1945 return (*max_idx_it)->GetIndex();
1946 }
1947}
1948
1949template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1951{
1952 if (this->mElements.begin() == this->mElements.end())
1953 {
1954 return UINT_MAX;
1955 }
1956 else
1957 {
1959
1960 const auto max_idx_it = std::max_element(this->mElements.begin(), this->mElements.end(),
1961 [](IbElem* const a, IbElem* const b)
1962 {
1963 return a->GetIndex() < b->GetIndex();
1964 });
1965
1966 return (*max_idx_it)->GetIndex();
1967 }
1968}
1969
1970template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1972{
1973 if (this->mLaminas.begin() == this->mLaminas.end())
1974 {
1975 return UINT_MAX;
1976 }
1977 else
1978 {
1979 using IbLam = ImmersedBoundaryElement<ELEMENT_DIM - 1, SPACE_DIM>;
1980
1981 const auto max_idx_it = std::max_element(this->mLaminas.begin(), this->mLaminas.end(),
1982 [](IbLam* const a, IbLam* const b)
1983 {
1984 return a->GetIndex() < b->GetIndex();
1985 });
1986
1987 return (*max_idx_it)->GetIndex();
1988 }
1989}
1990
1991template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
1993{
1994 if (!mLaminas.empty())
1995 {
1996 EXCEPTION("This method does not yet work in the presence of laminas");
1997 }
1998
1999 // Need a large double, but since we also perform calculations with it, we can't really use DBL_MAX
2000 const double LARGE_DOUBLE = 1e6;
2001
2002 /*
2003 * This method uses geometric information from a voronoi diagram of every node.
2004 *
2005 * Nodes with infinite voronoi cells must be in a boundary element.
2006 * In addition, some nodes in boundary elements will have unusually long shared-edges, and the voronoi perimeter
2007 * of boundary elements will exceed the true perimeter by an unusually large amount.
2008 * We use a threshold on the median values of these identifiers to robustly determine boundary elements.
2009 */
2010 UpdateNodeLocationsVoronoiDiagramIfOutOfDate();
2011
2012 // Get the maximum element index in the mesh
2013 const unsigned max_elem_idx = GetMaxElementIndex();
2014
2015 // Keep track of all finite shared edge lengths (between two elements), as well as (for each element) the maximum
2016 // shared edge length.
2017 std::vector<double> max_shared_lengths(1 + max_elem_idx, 0.0);
2018 std::vector<double> voronoi_perimeter(1 + max_elem_idx, 0.0);
2019
2020 // Loop over nodes and calculate values to fill the containers defined above
2021 for (const auto& p_node : this->mNodes)
2022 {
2023 const unsigned this_node_idx = p_node->GetIndex();
2024 const unsigned this_elem_idx = *p_node->ContainingElementsBegin();
2025
2026 // Get the voronoi cell that corresponds to this node
2027 const unsigned voronoi_cell_id = mVoronoiCellIdsIndexedByNodeIndex[this_node_idx];
2028 const auto& voronoi_cell = mNodeLocationsVoronoiDiagram.cells()[voronoi_cell_id];
2029
2030 // Iterate over the edges of this cell. Note that due to the halo region none of these edges should be infinite.
2031 auto p_edge = voronoi_cell.incident_edge();
2032
2033 // Loop over the voronoi edges associated with this node's voronoi cell
2034 do
2035 {
2036 // If the edge is infinite, the element containing it must be on the boundary
2037 if (p_edge->is_infinite())
2038 {
2039 max_shared_lengths[this_elem_idx] = LARGE_DOUBLE;
2040 voronoi_perimeter[this_elem_idx] += LARGE_DOUBLE;
2041 }
2042 else
2043 {
2044 // The global node index corresponding to a voronoi cell cell is encoded in its 'color' variable
2045 const unsigned twin_node_idx = p_edge->twin()->cell()->color();
2046 const unsigned twin_elem_idx = *this->GetNode(twin_node_idx)->ContainingElementsBegin();
2047
2048 // If the edge is between two elements, it counts
2049 if (this_elem_idx != twin_elem_idx)
2050 {
2051 const double edge_length = CalculateLengthOfVoronoiEdge(*p_edge);
2052 max_shared_lengths[this_elem_idx] = std::max(max_shared_lengths[this_elem_idx], edge_length);
2053 voronoi_perimeter[this_elem_idx] += edge_length;
2054 }
2055 }
2056 p_edge = p_edge->next();
2057 } while (p_edge != voronoi_cell.incident_edge());
2058 }
2059
2060 // Calculate what proportion bigger the voronoi perimeter is than the actual one
2061 std::vector<double> perimeter_multiples(voronoi_perimeter.size());
2062 for (const auto& p_elem : this->mElements)
2063 {
2064 const unsigned idx = p_elem->GetIndex();
2065 perimeter_multiples[idx] = voronoi_perimeter[idx] / this->GetSurfaceAreaOfElement(idx);
2066 }
2067
2068 // Calculate the medians of each vector. Copy the vector accounting for possible non-contiguous element indices.
2069 std::vector<double> copy_max_shared_lengths;
2070 std::vector<double> copy_perimeter_multiples;
2071
2072 for (const auto& p_elem : this->mElements)
2073 {
2074 const unsigned idx = p_elem->GetIndex();
2075 copy_max_shared_lengths.emplace_back(max_shared_lengths[idx]);
2076 copy_perimeter_multiples.emplace_back(perimeter_multiples[idx]);
2077 }
2078
2079 const std::size_t half_way = copy_max_shared_lengths.size() / 2;
2080 assert(half_way == copy_perimeter_multiples.size() / 2);
2081
2082 std::nth_element(copy_max_shared_lengths.begin(), copy_max_shared_lengths.begin() + half_way, copy_max_shared_lengths.end());
2083 std::nth_element(copy_perimeter_multiples.begin(), copy_perimeter_multiples.begin() + half_way, copy_perimeter_multiples.end());
2084
2085 double median_max_edge_length = copy_max_shared_lengths[half_way];
2086 double median_perimeter_multiple = copy_perimeter_multiples[half_way];
2087
2088 // In the event that most elements have infinite edges, for instance if there are a small number of elements,
2089 // a reduced median can be used
2090 if (median_max_edge_length == LARGE_DOUBLE || median_perimeter_multiple >= LARGE_DOUBLE)
2091 {
2092 median_max_edge_length = 0.5 * LARGE_DOUBLE;
2093 median_perimeter_multiple = 0.5 * LARGE_DOUBLE;
2094 }
2095
2096 // Finally, tag the boundary elements
2097 for (const auto& p_elem : this->mElements)
2098 {
2099 const unsigned idx = p_elem->GetIndex();
2100
2101 const bool large_shared_edge = max_shared_lengths[idx] > 1.1 * median_max_edge_length;
2102 const bool large_perimeter = perimeter_multiples[idx] > 1.1 * median_perimeter_multiple;
2103
2104 p_elem->SetIsBoundaryElement(large_shared_edge && large_perimeter);
2105 }
2106}
2107
2108template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
2110{
2111 if constexpr (SPACE_DIM == 2)
2112 {
2113 using boost_point = boost::polygon::point_data<int>;
2114
2115 /*
2116 * The voronoi diagram needs updating only if the node locations have changed (i.e. not more than once per
2117 * timestep). We test a chosen summary of node locations against the cached mSummaryOfNodeLocations to determine
2118 * this.
2119 */
2120 double new_location_summary = this->mNodes.front()->rGetLocation()[0] +
2121 this->mNodes.front()->rGetLocation()[1] +
2122 this->mNodes.back()->rGetLocation()[0] +
2123 this->mNodes.back()->rGetLocation()[1];
2124
2125 bool voronoi_needs_updating = std::fabs(mSummaryOfNodeLocations - new_location_summary) > DBL_EPSILON;
2126
2127 /*
2128 * Method outline:
2129 * - Add all node locations as boost points, correctly scaled
2130 * - Add extra locations for any nodes within distance mVoronoiHalo of the domain edge for periodicity
2131 * - Calculate the voronoi diagram
2132 * - Populate mVoronoiCellIdsIndexedByNodeIndex for efficient mapping between node index and corresponding voronoi
2133 * cell
2134 * - Store the corresponding node index in each voronoi cell's "color" variable for efficient reverse-lookup
2135 * - Tag boundary elements
2136 */
2137 if (voronoi_needs_updating)
2138 {
2139 mSummaryOfNodeLocations = new_location_summary;
2140
2141 // Helper c_vectors for halo region
2142 const c_vector<double, SPACE_DIM> halo_up = Create_c_vector(0.0, 1.0);
2143 const c_vector<double, SPACE_DIM> halo_down = Create_c_vector(0.0, -1.0);
2144 const c_vector<double, SPACE_DIM> halo_left = Create_c_vector(-1.0, 0.0);
2145 const c_vector<double, SPACE_DIM> halo_right = Create_c_vector(1.0, 0.0);
2146
2147 std::vector<std::pair<unsigned, c_vector<double, SPACE_DIM>>> halo_ids_and_locations;
2148 std::vector<unsigned> node_ids_in_source_idx_order;
2149
2150 // We need to translate node locations into boost points, which are scaled to an integer grid
2151 std::vector<boost_point> points;
2152
2153 // First add a point for each node, and calculate any additional locations needed within the halo
2154 for (const auto& p_node : this->mNodes)
2155 {
2156 const double x_pos = p_node->rGetLocation()[0];
2157 const double y_pos = p_node->rGetLocation()[1];
2158
2159 // Put the integer voronoi coordinate in for this node's location
2160 points.emplace_back(boost_point(ScaleUpToVoronoiCoordinate(x_pos), ScaleUpToVoronoiCoordinate(y_pos)));
2161 node_ids_in_source_idx_order.emplace_back(p_node->GetIndex());
2162
2163 // Now, for this location, decide whether any copies of this location are needed in the halo region
2164 const bool needed_up = y_pos < mVoronoiHalo;
2165 const bool needed_down = y_pos > 1.0 - mVoronoiHalo;
2166 const bool needed_left = x_pos > 1.0 - mVoronoiHalo;
2167 const bool needed_right = x_pos < mVoronoiHalo;
2168
2169 if (needed_up)
2170 {
2171 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2172 p_node->rGetLocation() + halo_up));
2173 }
2174 if (needed_down)
2175 {
2176 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2177 p_node->rGetLocation() + halo_down));
2178 }
2179 if (needed_left)
2180 {
2181 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2182 p_node->rGetLocation() + halo_left));
2183 }
2184 if (needed_right)
2185 {
2186 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2187 p_node->rGetLocation() + halo_right));
2188 }
2189 if (needed_up && needed_left)
2190 {
2191 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2192 p_node->rGetLocation() + halo_up + halo_left));
2193 }
2194 if (needed_up && needed_right)
2195 {
2196 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2197 p_node->rGetLocation() + halo_up + halo_right));
2198 }
2199 if (needed_down && needed_left)
2200 {
2201 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2202 p_node->rGetLocation() + halo_down + halo_left));
2203 }
2204 if (needed_down && needed_right)
2205 {
2206 halo_ids_and_locations.emplace_back(std::make_pair(p_node->GetIndex(),
2207 p_node->rGetLocation() + halo_down + halo_right));
2208 }
2209 }
2210
2211 // Next add the additional points
2212 for (const auto& pair : halo_ids_and_locations)
2213 {
2214 const unsigned node_idx = pair.first;
2215 c_vector<double, SPACE_DIM> location = pair.second;
2216
2217 const int x_coord = ScaleUpToVoronoiCoordinate(location[0]);
2218 const int y_coord = ScaleUpToVoronoiCoordinate(location[1]);
2219
2220 points.emplace_back(boost_point(x_coord, y_coord));
2221 node_ids_in_source_idx_order.emplace_back(node_idx);
2222 }
2223
2224 // Construct the voronoi diagram. This is the costly part of this method.
2225 mNodeLocationsVoronoiDiagram.clear();
2226 construct_voronoi(std::begin(points), std::end(points), &mNodeLocationsVoronoiDiagram);
2227
2228 // We need an efficient map from node global index to the voronoi cell representing it, and we can't assume
2229 // that the nodes are ordered sequentially. We first identify the largest node index.
2230 const unsigned max_node_idx = GetMaxNodeIndex();
2231
2232 mVoronoiCellIdsIndexedByNodeIndex.resize(1 + max_node_idx);
2233
2234 for (unsigned vor_cell_id = 0; vor_cell_id < mNodeLocationsVoronoiDiagram.cells().size(); ++vor_cell_id)
2235 {
2236 // Source index is incrementally given to each input point, which is in order of nodes in this->mNodes.
2237 // We need to be able to identify each vor_cell_id by the global node index
2238
2239 auto& r_this_cell = mNodeLocationsVoronoiDiagram.cells()[vor_cell_id];
2240 const auto source_idx = r_this_cell.source_index();
2241 const unsigned node_idx = node_ids_in_source_idx_order[source_idx];
2242
2243 r_this_cell.color(node_idx);
2244
2245 if (source_idx < this->mNodes.size())
2246 {
2247 mVoronoiCellIdsIndexedByNodeIndex[node_idx] = vor_cell_id;
2248 }
2249 }
2250
2251 // Finally, if the diagram was out-of-date, we will need to re-tag boundary elements.
2252 this->TagBoundaryElements();
2253 }
2254 }
2255 else
2256 {
2258 }
2259}
2260
2261template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
2263{
2264 assert(location >= -mVoronoiHalo);
2265 assert(location <= 1.0 + mVoronoiHalo);
2266
2267 constexpr auto DBL_INT_MIN = static_cast<double>(INT_MIN);
2268 constexpr auto DBL_INT_RANGE = static_cast<double>(INT_MAX) - static_cast<double>(INT_MIN);
2269
2270 // To handle periodicity, we add (again) any nodes within a halo region of the unit square
2271 constexpr double scale_factor = DBL_INT_RANGE / (1.0 + 2.0 * mVoronoiHalo);
2272
2273 // By construction there is no narrowing, so this static_cast might not be necessary
2274 return static_cast<int>(std::lround(DBL_INT_MIN + (mVoronoiHalo + location) * scale_factor));
2275}
2276
2277template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
2279{
2280 constexpr auto DBL_INT_RANGE = static_cast<double>(INT_MAX) - static_cast<double>(INT_MIN);
2281 constexpr double scale_factor = (1.0 + 2.0 * mVoronoiHalo) / DBL_INT_RANGE;
2282
2283 return scale_factor * distance;
2284}
2285
2286template<unsigned int ELEMENT_DIM, unsigned int SPACE_DIM>
2287const boost::polygon::voronoi_diagram<double>& ImmersedBoundaryMesh<ELEMENT_DIM, SPACE_DIM>::rGetNodeLocationsVoronoiDiagram(bool update)
2288{
2289 if (update)
2290 {
2291 this->UpdateNodeLocationsVoronoiDiagramIfOutOfDate();
2292 }
2293
2294 return mNodeLocationsVoronoiDiagram;
2295}
2296
2297template<unsigned int ELEMENT_DIM, unsigned int SPACE_DIM>
2299{
2300 return mVoronoiCellIdsIndexedByNodeIndex;
2301}
2302
2303// Explicit instantiation
2304template class ImmersedBoundaryMesh<1, 1>;
2305template class ImmersedBoundaryMesh<1, 2>;
2306template class ImmersedBoundaryMesh<1, 3>;
2307template class ImmersedBoundaryMesh<2, 2>;
2308template class ImmersedBoundaryMesh<2, 3>;
2309template class ImmersedBoundaryMesh<3, 3>;
2310
2311// Serialization for Boost >= 1.36
const double DOUBLE_UNSET
Definition Exception.hpp:57
#define EXCEPTION(message)
#define NEVER_REACHED
#define EXPORT_TEMPLATE_CLASS_ALL_DIMS(CLASS)
void SetAttribute(double attribute)
Node< SPACE_DIM > * GetNode(unsigned localIndex) const
std::vector< double > & rGetElementAttributes()
double GetNodeLocation(unsigned localIndex, unsigned dimension) const
unsigned GetNumElementAttributes()
unsigned GetNumNodes() const
unsigned GetNodeGlobalIndex(unsigned localIndex) const
unsigned GetIndex() const
virtual bool HasNodePermutation()
bool mMeshChangesDuringSimulation
std::vector< Node< SPACE_DIM > * > mNodes
std::shared_ptr< FluidSource< SPACE_DIM > > GetFluidSource()
std::vector< Node< SPACE_DIM > * > & rGetCornerNodes()
void SetIsBoundaryElement(bool isBoundaryElement)
ImmersedBoundaryElementData GetNextImmersedBoundaryElementData()
ImmersedBoundaryElementData GetNextImmersedBoundaryLaminaData()
ChasteCuboid< SPACE_DIM > CalculateBoundingBoxOfElement(unsigned index)
std::vector< ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * > mElements
void ReMesh(bool randomOrder=false)
virtual double GetVolumeOfElement(unsigned index)
double GetCharacteristicNodeSpacing() const
void SetNeighbourDist(double neighbourDist)
bool NodesInDifferentElementOrLamina(Node< SPACE_DIM > *pNodeA, Node< SPACE_DIM > *pNodeB)
unsigned DivideElement(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, unsigned nodeAIndex, unsigned nodeBIndex, c_vector< double, SPACE_DIM > centroid, c_vector< double, SPACE_DIM > axisOfDivision)
const std::vector< Node< SPACE_DIM > * > & rGetNodes() const
unsigned GetMaxElementIndex() const
multi_array< double, 3 > & rGetModifiable2dVelocityGrids()
unsigned SolveElementMapping(unsigned index) const
ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * GetLamina(unsigned index) const
void ReMeshLamina(ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > *pLamina, bool randomOrder)
void SetNode(unsigned nodeIndex, ChastePoint< SPACE_DIM > point)
double GetSkewnessOfElementMassDistributionAboutAxis(unsigned elemIndex, c_vector< double, SPACE_DIM > axis)
void SetNumGridPtsXAndY(unsigned numGridPts)
double ScaleDistanceDownFromVoronoi(const double distance) const
void SetCharacteristicNodeSpacing(double nodeSpacing)
std::array< unsigned, 13 > GetPolygonDistribution()
double CalculateLengthOfVoronoiEdge(const boost::polygon::voronoi_diagram< double >::edge_type &rEdge)
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > mElementFluidSources
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > & rGetElementFluidSources()
virtual unsigned GetNumNodes() const
double GetElongationShapeFactorOfElement(unsigned elementIndex)
unsigned SolveNodeMapping(unsigned index) const
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > mBalancingFluidSources
unsigned AddNode(Node< SPACE_DIM > *pNewNode)
double GetAverageNodeSpacingOfElement(unsigned index, bool recalculate=true)
virtual c_vector< double, 3 > CalculateMomentsOfElement(unsigned index)
void ConformToGeometry(c_vector< double, SPACE_DIM > &rLocation)
const boost::polygon::voronoi_diagram< double > & rGetNodeLocationsVoronoiDiagram(bool update=true)
void ReMeshElement(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, bool randomOrder)
multi_array< double, 3 > m2dVelocityGrids
std::set< unsigned > GetNeighbouringNodeIndices(unsigned nodeIndex)
unsigned DivideElementAlongGivenAxis(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, c_vector< double, SPACE_DIM > axisOfDivision, bool placeOriginalElementBelow=false)
virtual unsigned GetNumElements() const
void SetNumGridPtsX(unsigned meshPointsX)
std::vector< std::shared_ptr< FluidSource< SPACE_DIM > > > & rGetBalancingFluidSources()
double GetAverageNodeSpacingOfLamina(unsigned index, bool recalculate=true)
int ScaleUpToVoronoiCoordinate(double location) const
unsigned SolveBoundaryElementMapping(unsigned index) const
unsigned DivideElementAlongShortAxis(ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > *pElement, bool placeOriginalElementBelow=false)
virtual c_vector< double, SPACE_DIM > GetCentroidOfElement(unsigned index)
void SetElementDivisionSpacing(double elementDivisionSpacing)
c_vector< double, SPACE_DIM > GetShortAxisOfElement(unsigned index)
std::set< unsigned > GetNeighbouringElementIndices(unsigned elemIdx)
const multi_array< double, 3 > & rGet2dVelocityGrids() const
std::vector< ImmersedBoundaryElement< ELEMENT_DIM - 1, SPACE_DIM > * > mLaminas
void ConstructFromMeshReader(AbstractMeshReader< ELEMENT_DIM, SPACE_DIM > &rMeshReader)
ImmersedBoundaryElement< ELEMENT_DIM, SPACE_DIM > * GetElement(unsigned index) const
double GetVoronoiSurfaceAreaOfElement(unsigned elemIdx)
virtual double GetSurfaceAreaOfElement(unsigned index)
const std::vector< unsigned int > & GetVoronoiCellIdsIndexedByNodeIndex() const
c_vector< double, SPACE_DIM > GetVectorFromAtoB(const c_vector< double, SPACE_DIM > &rLocation1, const c_vector< double, SPACE_DIM > &rLocation2)
void SetNumGridPtsY(unsigned meshPointsY)
unsigned GetNumAllElements() const
Definition Node.hpp:59
void SetIndex(unsigned index)
Definition Node.cpp:121
ContainingElementIterator ContainingElementsBegin() const
Definition Node.hpp:485
unsigned GetRegion() const
Definition Node.cpp:437
static RandomNumberGenerator * Instance()
unsigned randMod(unsigned base)