Chaste Commit::29c3c4ab2bee00ddd311a035a1e522c03cbe928a
ImmersedBoundaryCellPopulation.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 "ImmersedBoundaryCellPopulation.hpp"
37
38#include <iomanip>
39
40#include "ApoptoticCellProperty.hpp"
41#include "CellPopulationElementWriter.hpp"
42#include "ImmersedBoundaryMeshWriter.hpp"
43#include "ImmersedBoundaryBoundaryCellWriter.hpp"
44#include "ShortAxisImmersedBoundaryDivisionRule.hpp"
45#include "Warnings.hpp"
46
47template <unsigned DIM>
50 std::vector<CellPtr>& rCells,
51 bool deleteMesh,
52 bool validate,
53 const std::vector<unsigned> locationIndices)
54 : AbstractOffLatticeCellPopulation<DIM>(rMesh, rCells, locationIndices),
55 mDeleteMesh(deleteMesh),
56 mIntrinsicSpacing(0.01),
57 mPopulationHasActiveSources(false),
58 mOutputNodeRegionToVtk(false),
59 mReMeshFrequency(UINT_MAX)
60{
63
64 /*
65 * If no location indices are specified, associate with elements from the
66 * mesh (assumed to be sequentially ordered).
67 */
68 std::list<CellPtr>::iterator it = this->mCells.begin();
69 for (unsigned i = 0; it != this->mCells.end(); ++it, ++i)
70 {
71 // Assume that the ordering matches
72 unsigned index = locationIndices.empty() ? i : locationIndices[i];
74 }
75
76 // Check each element has only one cell attached
77 if (validate)
78 {
79 Validate();
80 }
81
83
84 // Set the mesh division spacing distance
87}
88
89template <unsigned DIM>
93 mDeleteMesh(true),
94 mIntrinsicSpacing(0.01),
95 mPopulationHasActiveSources(false),
96 mOutputNodeRegionToVtk(false),
97 mReMeshFrequency(UINT_MAX)
98{
100}
101
102template <unsigned DIM>
104{
105 if (mDeleteMesh)
106 {
107 delete &this->mrMesh;
108 }
109}
110
111template <unsigned DIM>
113{
114 double average_intrinsic_size = 0.0;
115
116 for (auto elem_iter = mpImmersedBoundaryMesh->GetElementIteratorBegin();
117 elem_iter != mpImmersedBoundaryMesh->GetElementIteratorEnd();
118 ++elem_iter)
119 {
120 average_intrinsic_size += mpImmersedBoundaryMesh->GetVolumeOfElement(elem_iter->GetIndex());
121 }
122
123 return sqrt(average_intrinsic_size / mpImmersedBoundaryMesh->GetNumElements());
124}
125
126template <unsigned DIM>
128{
129 return 0.0;
130}
131
132template <unsigned DIM>
137
138template <unsigned DIM>
140{
141 return *mpImmersedBoundaryMesh;
142}
143
144template <unsigned DIM>
146{
147 return mpImmersedBoundaryMesh->GetElement(elementIndex);
148}
149
150template <unsigned DIM>
152{
153 return mpImmersedBoundaryMesh->GetLamina(laminaIndex);
154}
155
156template <unsigned DIM>
158{
159 return this->mrMesh.GetNumNodes();
160}
161
162template <unsigned DIM>
164{
165 return mpImmersedBoundaryMesh->GetCentroidOfElement(this->mCellLocationMap[pCell.get()]);
166}
167
168template <unsigned DIM>
170{
171 return this->mrMesh.GetNode(index);
172}
173
174template <unsigned DIM>
176{
177 assert(newDistance >= 0.0);
178 mInteractionDistance = newDistance;
179}
180
181template <unsigned DIM>
183{
184 return mInteractionDistance;
185}
186
187template <unsigned DIM>
189{
190 mReMeshFrequency = newFrequency;
191}
192
193template <unsigned DIM>
195{
196 return mReMeshFrequency;
197}
198
199template <unsigned DIM>
201{
202 return mIntrinsicSpacing;
203}
204
205template <unsigned DIM>
207 CellPtr pCell)
208{
209 return mpImmersedBoundaryMesh->GetNeighbouringElementIndices(this->GetLocationIndexUsingCell(pCell));
210}
211
212template <unsigned DIM>
214{
215 if (pNewNode == nullptr)
216 {
217 return 0;
218 }
219 return mpImmersedBoundaryMesh->AddNode(pNewNode);
220}
221
222template <unsigned DIM>
224{
225 mpImmersedBoundaryMesh->SetNode(nodeIndex, rNewLocation);
226}
227
228template <unsigned DIM>
230 CellPtr pCell)
231{
232 return mpImmersedBoundaryMesh->GetElement(this->GetLocationIndexUsingCell(pCell));
233}
234
235template <unsigned DIM>
237{
238 return mpImmersedBoundaryMesh->GetNumElements();
239}
240
241template <unsigned DIM>
243{
244 return mpImmersedBoundaryMesh->GetNumLaminas();
245}
246
247template <unsigned DIM>
249 CellPtr pNewCell,
250 CellPtr pParentCell)
251{
252 // Get the element associated with this cell
253 ImmersedBoundaryElement<DIM, DIM>* p_element = GetElementCorrespondingToCell(pParentCell);
254
255 // Get the orientation of division
256 c_vector<double, DIM> division_vector = mpImmersedBoundaryDivisionRule->CalculateCellDivisionVector(pParentCell, *this);
257
258 // Divide the element
259 unsigned new_elem_idx = mpImmersedBoundaryMesh->DivideElementAlongGivenAxis(p_element, division_vector, true);
260
261 // Associate the new cell with the element
262 this->mCells.push_back(pNewCell);
263
264 // Update location cell map
265 CellPtr p_created_cell = this->mCells.back();
266 this->SetCellUsingLocationIndex(new_elem_idx, p_created_cell);
267 this->mCellLocationMap[p_created_cell.get()] = new_elem_idx;
268
269 return p_created_cell;
270}
271
272template <unsigned DIM>
274{
275 unsigned num_removed = 0;
276
277 for (std::list<CellPtr>::iterator it = this->mCells.begin();
278 it != this->mCells.end();
279 )
280 {
281 if ((*it)->IsDead())
282 {
283 // Count the cell as dead
284 num_removed++;
285
286 if (!(this->GetElement(this->GetLocationIndexUsingCell((*it)))->IsDeleted()))
287 {
288 this->GetElement(this->GetLocationIndexUsingCell(*it))->MarkAsDeleted();
289 }
290
291 // Delete the cell
292 it = this->mCells.erase(it);
293 }
294 else
295 {
296 ++it;
297 }
298 }
299 return num_removed;
300}
301
302template <unsigned DIM>
304 [[maybe_unused]] double dt) // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
305{
306 if constexpr (DIM == 2)
307 {
308 // Helper variables, pre-declared for efficiency
309 unsigned num_grid_pts_x = this->rGetMesh().GetNumGridPtsX();
310 unsigned num_grid_pts_y = this->rGetMesh().GetNumGridPtsY();
311
312 double characteristic_spacing = this->rGetMesh().GetCharacteristicNodeSpacing();
313 double grid_spacing_x = 1.0 / (double)num_grid_pts_x;
314 double grid_spacing_y = 1.0 / (double)num_grid_pts_y;
315
316 unsigned first_idx_x;
317 unsigned first_idx_y;
318
319 std::vector<unsigned> x_indices(4);
320 std::vector<unsigned> y_indices(4);
321
322 std::vector<double> x_deltas(4);
323 std::vector<double> y_deltas(4);
324
325 double delta;
326
327 c_vector<double, DIM> displacement = zero_vector<double>(DIM);
328
329 // Get references to the fluid velocity grid
330 const multi_array<double, 3>& vel_grids = this->rGetMesh().rGet2dVelocityGrids();
331
332 // Iterate over all nodes
333 c_vector<double, DIM> node_location;
334 for (auto node_iter = this->rGetMesh().GetNodeIteratorBegin(false);
335 node_iter != this->rGetMesh().GetNodeIteratorEnd();
336 ++node_iter)
337 {
338 // Get location of current node
339 node_location = node_iter->rGetLocation();
340
341 // Get first grid index in each dimension, taking account of possible wrap-around
342 first_idx_x = unsigned(floor(node_location[0] / grid_spacing_x)) + num_grid_pts_x - 1;
343 first_idx_y = unsigned(floor(node_location[1] / grid_spacing_y)) + num_grid_pts_y - 1;
344
345 // Calculate all four indices and deltas in each dimension
346 for (unsigned i = 0; i < 4; i++)
347 {
348 x_indices[i] = (first_idx_x + i) % num_grid_pts_x;
349 y_indices[i] = (first_idx_y + i) % num_grid_pts_y;
350
351 x_deltas[i] = Delta1D(fabs(x_indices[i] * grid_spacing_x - node_location[0]), grid_spacing_x);
352 y_deltas[i] = Delta1D(fabs(y_indices[i] * grid_spacing_x - node_location[1]), grid_spacing_y);
353 }
354
355 // Loop over the 4x4 grid which will influence the displacement of the current node
356 for (unsigned x_idx = 0; x_idx < 4; ++x_idx)
357 {
358 for (unsigned y_idx = 0; y_idx < 4; ++y_idx)
359 {
360 // The applied velocity is weighted by the delta function
361 delta = x_deltas[x_idx] * y_deltas[y_idx];
362 displacement[0] += vel_grids[0][x_indices[x_idx]][y_indices[y_idx]] * delta;
363 displacement[1] += vel_grids[1][x_indices[x_idx]][y_indices[y_idx]] * delta;
364 }
365 }
366
367 // Normalise by timestep
368 displacement *= dt;
369
370 // If the displacement is too big, warn the user once and scale it back
371 if (norm_2(displacement) > characteristic_spacing)
372 {
373 if (norm_2(displacement) > 10.0 * characteristic_spacing)
374 {
375 EXCEPTION("Nodes are moving more than 10x CharacteristicNodeSpacing. Aborting.");
376 }
377
378 WARN_ONCE_ONLY("Nodes are moving more than the CharacteristicNodeSpacing. This could cause elements to become inverted so the motion has been restricted. Use a smaller timestep to avoid these warnings.");
379 displacement *= characteristic_spacing / norm_2(displacement);
380 }
381
382 // Get new node location
383 node_location += displacement;
384
385 // Account for periodic boundary
386 for (unsigned i = 0; i < DIM; ++i)
387 {
388 node_location[i] = fmod(node_location[i] + 1.0, 1.0);
389 }
390
391 // Create ChastePoint for new node location
392 ChastePoint<DIM> new_point(node_location);
393
394 // Move the node
395 this->SetNode(node_iter->GetIndex(), new_point);
396 }
397
398 // If active sources, we need to update those location as well
399 if (this->DoesPopulationHaveActiveSources())
400 {
401 std::vector<std::shared_ptr<FluidSource<DIM>>>& r_element_sources = this->rGetMesh().rGetElementFluidSources();
402 std::vector<std::shared_ptr<FluidSource<DIM>>>& r_balance_sources = this->rGetMesh().rGetBalancingFluidSources();
403
404 // Construct a vector of all sources combined
405 std::vector<std::shared_ptr<FluidSource<DIM>>> combined_sources;
406 combined_sources.insert(combined_sources.end(), r_element_sources.begin(), r_element_sources.end());
407 combined_sources.insert(combined_sources.end(), r_balance_sources.begin(), r_balance_sources.end());
408
409 c_vector<double, DIM> source_location;
410
411 // Iterate over all sources and update their locations
412 for (unsigned source_idx = 0; source_idx < combined_sources.size(); source_idx++)
413 {
414 // Get location of current node
415 source_location = combined_sources[source_idx]->rGetLocation();
416
417 // Get first grid index in each dimension, taking account of possible wrap-around
418 first_idx_x = unsigned(floor(source_location[0] / grid_spacing_x)) + num_grid_pts_x - 1;
419 first_idx_y = unsigned(floor(source_location[1] / grid_spacing_y)) + num_grid_pts_y - 1;
420
421 // Calculate all four indices and deltas in each dimension
422 for (unsigned i = 0; i < 4; ++i)
423 {
424 x_indices[i] = (first_idx_x + i) % num_grid_pts_x;
425 y_indices[i] = (first_idx_y + i) % num_grid_pts_y;
426
427 x_deltas[i] = Delta1D(fabs(x_indices[i] * grid_spacing_x - source_location[0]), grid_spacing_x);
428 y_deltas[i] = Delta1D(fabs(y_indices[i] * grid_spacing_x - source_location[1]), grid_spacing_y);
429 }
430
431 // Loop over the 4x4 grid which will influence the displacement of the current node
432 for (unsigned x_idx = 0; x_idx < 4; ++x_idx)
433 {
434 for (unsigned y_idx = 0; y_idx < 4; ++y_idx)
435 {
436 // The applied velocity is weighted by the delta function
437 delta = x_deltas[x_idx] * y_deltas[y_idx];
438 displacement[0] += vel_grids[0][x_indices[x_idx]][y_indices[y_idx]] * delta;
439 displacement[1] += vel_grids[1][x_indices[x_idx]][y_indices[y_idx]] * delta;
440 }
441 }
442
443 // Normalise by timestep
444 displacement *= dt;
445
446 // If the displacement is too big, warn the user once and scale it back
447 if (norm_2(displacement) > characteristic_spacing)
448 {
449 if (norm_2(displacement) > 10.0 * characteristic_spacing)
450 {
451 EXCEPTION("Sources are moving more than 10x CharacteristicNodeSpacing. Aborting.");
452 }
453
454 WARN_ONCE_ONLY("Sources are moving more than the CharacteristicNodeSpacing. This could cause elements to become inverted so the motion has been restricted. Use a smaller timestep to avoid these warnings.");
455 displacement *= characteristic_spacing / norm_2(displacement);
456 }
457
458 // Get new node location
459 source_location += displacement;
460
461 // Account for periodic boundary
462 for (unsigned i = 0; i < DIM; ++i)
463 {
464 source_location[i] = fmod(source_location[i] + 1.0, 1.0);
465 }
466
467 // Move the node
468 combined_sources[source_idx]->rGetModifiableLocation() = source_location;
469 }
470 }
471
472 // Finally, call ReMesh if required
473 const auto num_time_steps = SimulationTime::Instance()->GetTimeStepsElapsed();
474 if (num_time_steps > 0 && num_time_steps % mReMeshFrequency == 0)
475 {
476 mpImmersedBoundaryMesh->ReMesh();
477 }
478 }
479 else
480 {
482 }
483}
484
485template <unsigned DIM>
486double ImmersedBoundaryCellPopulation<DIM>::Delta1D(double dist, double spacing)
487{
488 return (0.25 * (1.0 + cos(M_PI * dist / (2 * spacing))));
489}
490
491template <unsigned DIM>
493{
494 return GetElementCorrespondingToCell(pCell)->IsDeleted();
495}
496
497template <unsigned DIM>
498void ImmersedBoundaryCellPopulation<DIM>::Update(bool hasHadBirthsOrDeaths)
499{
500 // If the first cell has target atea property, assume there is a target area modifier in place
501 if (this->Begin()->GetCellData()->HasItem("target area"))
502 {
503 for (auto cell_iter = this->Begin();
504 cell_iter != this->End();
505 ++cell_iter)
506 {
507 double target_area = cell_iter->GetCellData()->GetItem("target area");
508 double actual_area = this->GetVolumeOfCell(*cell_iter);
509
510 double strength = 1e-2 * (target_area - actual_area) / target_area;
511
512 this->GetElementCorrespondingToCell(*cell_iter)->GetFluidSource()->SetStrength(strength);
513 }
514 }
515}
516
517template <unsigned DIM>
519{
520 // Check each element has only one cell attached
521 std::vector<unsigned> validated_element = std::vector<unsigned>(this->GetNumElements(), 0);
522 for (auto cell_iter = this->Begin();
523 cell_iter != this->End();
524 ++cell_iter)
525 {
526 unsigned elem_index = this->GetLocationIndexUsingCell(*cell_iter);
527 validated_element[elem_index]++;
528 }
529
530 for (unsigned i = 0; i < validated_element.size(); ++i)
531 {
532 if (validated_element[i] == 0)
533 {
534 EXCEPTION("At time " << SimulationTime::Instance()->GetTime() << ", Element " << i << " does not appear to have a cell associated with it");
535 }
536
537 if (validated_element[i] > 1)
538 {
539 // This should never be reached as you can only set one cell per element index
541 EXCEPTION("At time " << SimulationTime::Instance()->GetTime() << ", Element " << i << " appears to have " << validated_element[i] << " cells associated with it"); //LCOV_EXCL_LINE
542 }
543 }
544}
545
546template <unsigned DIM>
548 boost::shared_ptr<AbstractCellPopulationWriter<DIM, DIM> > pPopulationWriter)
549{
550 pPopulationWriter->Visit(this);
551}
552
553template <unsigned DIM>
555 boost::shared_ptr<AbstractCellPopulationEventWriter<DIM, DIM> > pPopulationEventWriter)
556{
557 pPopulationEventWriter->Visit(this);
558}
559
560template <unsigned DIM>
562 boost::shared_ptr<AbstractCellPopulationCountWriter<DIM, DIM> > pPopulationCountWriter)
563{
564 pPopulationCountWriter->Visit(this);
565}
566
567template <unsigned DIM>
569 boost::shared_ptr<AbstractCellWriter<DIM, DIM> > pCellWriter, CellPtr pCell)
570{
571 pCellWriter->VisitCell(pCell, this);
572}
573
574template <unsigned DIM>
576{
577 // Get the element index corresponding to this cell
578 unsigned elem_index = this->GetLocationIndexUsingCell(pCell);
579
580 // Get the cell's volume from the immersed boundary mesh
581 double cell_volume = mpImmersedBoundaryMesh->GetVolumeOfElement(elem_index);
582
583 return cell_volume;
584}
585
586template <unsigned DIM>
588 const std::string& rDirectory)
589{
590 // Create mesh writer for VTK output
591 ImmersedBoundaryMeshWriter<DIM, DIM> mesh_writer(rDirectory, "results", false);
592
593 // Find the cell overlap information, and get the number of cell parts needed for each element
594 mesh_writer.FindElementOverlaps(*mpImmersedBoundaryMesh);
595 const std::vector<std::vector<unsigned>>& r_elem_parts = mesh_writer.rGetElementParts();
596
597 // Iterate over any cell writers that are present
598 for (auto cell_writer_iter = this->mCellWriters.begin();
599 cell_writer_iter != this->mCellWriters.end();
600 ++cell_writer_iter)
601 {
602 // Create vector to store VTK cell data
603 std::vector<double> vtk_cell_data;
604
605 // Iterate over immersed boundary elements
606 for (auto elem_iter = mpImmersedBoundaryMesh->GetElementIteratorBegin();
607 elem_iter != mpImmersedBoundaryMesh->GetElementIteratorEnd();
608 ++elem_iter)
609 {
610 /*
611 * Get index of this element in the mesh, and the number of parts it
612 * is broken into for visualisation.
613 */
614 const unsigned elem_index = elem_iter->GetIndex();
615 const auto num_elem_parts = r_elem_parts[elem_index].empty() ? 1 : r_elem_parts[elem_index].size();
616
617 // Get the cell corresponding to this element
618 CellPtr p_cell = this->GetCellUsingLocationIndex(elem_index);
619 assert(p_cell);
620
621 /*
622 * Populate the vector of VTK cell data. We loop over the number of
623 * output cells as this takes into account that some elements will
624 * be broken into pieces for visualisation.
625 */
626 for (unsigned elem_part = 0; elem_part < num_elem_parts; ++elem_part)
627 {
628 vtk_cell_data.push_back((*cell_writer_iter)->GetCellDataForVtkOutput(p_cell, this));
629 }
630 }
631
632 /*
633 * Iterate over immersed boundary laminas (no associated cell) to ensure
634 * vtk_cell_data is the correct size.
635 */
636 for (auto lam_iter = mpImmersedBoundaryMesh->GetLaminaIteratorBegin();
637 lam_iter != mpImmersedBoundaryMesh->GetLaminaIteratorEnd();
638 ++lam_iter)
639 {
640 vtk_cell_data.push_back(-1.0);
641 }
642
643 mesh_writer.AddCellData((*cell_writer_iter)->GetVtkCellDataName(), vtk_cell_data);
644 }
645
646 /*
647 * When outputting any CellData, we assume that the first cell is
648 * representative of all cells.
649 */
650 const unsigned num_cell_data_items = this->Begin()->GetCellData()->GetNumItems();
651 std::vector<std::string> cell_data_names = this->Begin()->GetCellData()->GetKeys();
652
653 std::vector<std::vector<double>> cell_data;
654 for (unsigned var = 0; var < num_cell_data_items; ++var)
655 {
656 std::vector<double> cell_data_var;
657 cell_data.push_back(cell_data_var);
658 }
659
660 // Iterate over immersed boundary elements
661 for (auto elem_iter = mpImmersedBoundaryMesh->GetElementIteratorBegin();
662 elem_iter != mpImmersedBoundaryMesh->GetElementIteratorEnd();
663 ++elem_iter)
664 {
665 /*
666 * Get index of this element in the mesh, and the number of parts it is
667 * broken into for visualisation.
668 */
669 const unsigned elem_index = elem_iter->GetIndex();
670 const auto num_elem_parts = r_elem_parts[elem_index].empty() ? 1 : r_elem_parts[elem_index].size();
671
672 // Get the cell corresponding to this element
673 CellPtr p_cell = this->GetCellUsingLocationIndex(elem_index);
674 assert(p_cell);
675
676 for (unsigned var = 0; var < num_cell_data_items; var++)
677 {
678 /*
679 * Populate the vector of VTK cell data. We loop over the number of
680 * output cells as this takes into account that some elements will
681 * be broken into pieces for visualisation.
682 */
683 for (unsigned elem_part = 0; elem_part < num_elem_parts; ++elem_part)
684 {
685 cell_data[var].push_back(p_cell->GetCellData()->GetItem(cell_data_names[var]));
686 }
687 }
688 }
689
690 /*
691 * Iterate over immersed boundary laminas (no associated cell) to ensure
692 * cell_data is the correct size.
693 */
694 for (auto lam_iter = mpImmersedBoundaryMesh->GetLaminaIteratorBegin();
695 lam_iter != mpImmersedBoundaryMesh->GetLaminaIteratorEnd();
696 ++lam_iter)
697 {
698 for (unsigned var = 0; var < num_cell_data_items; ++var)
699 {
700 cell_data[var].push_back(DOUBLE_UNSET);
701 }
702 }
703
704 for (unsigned var = 0; var < num_cell_data_items; ++var)
705 {
706 mesh_writer.AddCellData(cell_data_names[var], cell_data[var]);
707 }
708
709 // Write node regions
710 if (mOutputNodeRegionToVtk)
711 {
712 std::vector<double> node_regions;
713 for (auto node_iter = mpImmersedBoundaryMesh->GetNodeIteratorBegin();
714 node_iter != mpImmersedBoundaryMesh->GetNodeIteratorEnd();
715 ++node_iter)
716 {
717 node_regions.push_back(static_cast<double>(node_iter->GetRegion()));
718 }
719 mesh_writer.AddPointData("Node Regions", node_regions);
720 }
721
722 unsigned num_timesteps = SimulationTime::Instance()->GetTimeStepsElapsed();
723 std::stringstream time;
724 time << num_timesteps;
725
726 mesh_writer.WriteVtkUsingMesh(*mpImmersedBoundaryMesh, time.str());
727
728 *(this->mpVtkMetaFile) << " <DataSet timestep=\"";
729 *(this->mpVtkMetaFile) << num_timesteps;
730 *(this->mpVtkMetaFile) << "\" group=\"\" part=\"0\" file=\"results_";
731 *(this->mpVtkMetaFile) << num_timesteps;
732 *(this->mpVtkMetaFile) << ".vtu\"/>\n";
733}
734
735template <unsigned DIM>
737 OutputFileHandler& rOutputFileHandler)
738{
739 if (this->mOutputResultsForChasteVisualizer)
740 {
741 if (!this->template HasWriter<CellPopulationElementWriter>())
742 {
743 this->template AddPopulationWriter<CellPopulationElementWriter>();
744 }
745 }
746
748}
749
750template <unsigned DIM>
752 out_stream& rParamsFile)
753{
754 // Add the division rule parameters
755 *rParamsFile << "\t\t<ImmersedBoundaryDivisionRule>\n";
756 mpImmersedBoundaryDivisionRule->OutputCellImmersedBoundaryDivisionRuleInfo(rParamsFile);
757 *rParamsFile << "\t\t</ImmersedBoundaryDivisionRule>\n";
758
759 // Call method on direct parent class
761}
762
763template <unsigned DIM>
764double ImmersedBoundaryCellPopulation<DIM>::GetWidth(const unsigned& rDimension)
765{
766 double width = this->mrMesh.GetWidth(rDimension);
767 return width;
768}
769
770template <unsigned DIM>
772 unsigned index)
773{
774 return mpImmersedBoundaryMesh->GetNeighbouringNodeIndices(index);
775}
776
777template <unsigned DIM>
779{
780 // This method only works in 2D sequential
781 assert(PetscTools::IsSequential());
782 if constexpr (DIM == 2)
783 {
784 unsigned num_vertex_nodes = mpImmersedBoundaryMesh->GetNumNodes();
785 unsigned num_vertex_elements = mpImmersedBoundaryMesh->GetNumElements();
786
787 std::string mesh_file_name = "mesh";
788
789 // Get a unique temporary foldername
790 std::stringstream pid;
791 pid << getpid();
792 OutputFileHandler output_file_handler("2D_temporary_tetrahedral_mesh_" + pid.str());
793 std::string output_dir = output_file_handler.GetOutputDirectoryFullPath();
794
795 // Compute the number of nodes in the TetrahedralMesh
796 unsigned num_tetrahedral_nodes = num_vertex_nodes + num_vertex_elements;
797
798 // Write node file
799 out_stream p_node_file = output_file_handler.OpenOutputFile(mesh_file_name+".node");
800 (*p_node_file) << std::scientific;
801 (*p_node_file) << std::setprecision(20);
802 (*p_node_file) << num_tetrahedral_nodes << "\t2\t0\t1" << std::endl;
803
804 // Begin by writing each node in the VertexMesh
805 auto nodes = mpImmersedBoundaryMesh->rGetNodes();
806 for (auto p_node : nodes)
807 {
808 unsigned index = p_node->GetIndex();
809 const c_vector<double, DIM>& r_location = p_node->rGetLocation();
810 unsigned is_boundary_node = p_node->IsBoundaryNode() ? 1 : 0;
811
812 (*p_node_file) << index << "\t" << r_location[0] << "\t" << r_location[1] << "\t" << is_boundary_node << std::endl;
813 }
814
815 // Now write an additional node at each ImmersedBoundaryElement's centroid
816 unsigned num_tetrahedral_elements = 0;
817 for (unsigned vertex_elem_index = 0;
818 vertex_elem_index < num_vertex_elements;
819 ++vertex_elem_index)
820 {
821 unsigned index = num_vertex_nodes + vertex_elem_index;
822
823 c_vector<double, DIM> location = mpImmersedBoundaryMesh->GetCentroidOfElement(vertex_elem_index);
824
825 // Any node located at a ImmersedBoundaryElement's centroid will not be a boundary node
826 unsigned is_boundary_node = 0;
827 (*p_node_file) << index << "\t" << location[0] << "\t" << location[1] << "\t" << is_boundary_node << std::endl;
828
829 // Also keep track of how many tetrahedral elements there will be
830 num_tetrahedral_elements += mpImmersedBoundaryMesh->GetElement(vertex_elem_index)->GetNumNodes();
831 }
832 p_node_file->close();
833
834 // Write element file
835 out_stream p_elem_file = output_file_handler.OpenOutputFile(mesh_file_name+".ele");
836 (*p_elem_file) << std::scientific;
837 (*p_elem_file) << num_tetrahedral_elements << "\t3\t0" << std::endl;
838
839 std::set<std::pair<unsigned, unsigned> > tetrahedral_edges;
840
841 unsigned tetrahedral_elem_index = 0;
842 for (unsigned vertex_elem_index = 0;
843 vertex_elem_index < num_vertex_elements;
844 ++vertex_elem_index)
845 {
846 ImmersedBoundaryElement<DIM, DIM>* p_vertex_element = mpImmersedBoundaryMesh->GetElement(vertex_elem_index);
847
848 // Iterate over nodes owned by this ImmersedBoundaryElement
849 unsigned num_nodes_in_vertex_element = p_vertex_element->GetNumNodes();
850 for (unsigned local_index = 0;
851 local_index < num_nodes_in_vertex_element;
852 ++local_index)
853 {
854 unsigned node_0_index = p_vertex_element->GetNodeGlobalIndex(local_index);
855 unsigned node_1_index = p_vertex_element->GetNodeGlobalIndex((local_index+1)%num_nodes_in_vertex_element);
856 unsigned node_2_index = num_vertex_nodes + vertex_elem_index;
857
858 (*p_elem_file) << tetrahedral_elem_index++ << "\t" << node_0_index << "\t" << node_1_index << "\t" << node_2_index << std::endl;
859
860 // Add edges to the set if they are not already present
861 std::pair<unsigned, unsigned> edge_0 = this->CreateOrderedPair(node_0_index, node_1_index);
862 std::pair<unsigned, unsigned> edge_1 = this->CreateOrderedPair(node_1_index, node_2_index);
863 std::pair<unsigned, unsigned> edge_2 = this->CreateOrderedPair(node_2_index, node_0_index);
864
865 tetrahedral_edges.insert(edge_0);
866 tetrahedral_edges.insert(edge_1);
867 tetrahedral_edges.insert(edge_2);
868 }
869 }
870 p_elem_file->close();
871
872 // Write edge file
873 out_stream p_edge_file = output_file_handler.OpenOutputFile(mesh_file_name+".edge");
874 (*p_edge_file) << std::scientific;
875 (*p_edge_file) << tetrahedral_edges.size() << "\t1" << std::endl;
876
877 unsigned edge_index = 0;
878 for (auto edge_iter = tetrahedral_edges.begin();
879 edge_iter != tetrahedral_edges.end();
880 ++edge_iter)
881 {
882 std::pair<unsigned, unsigned> this_edge = *edge_iter;
883
884 // To be a boundary edge both nodes need to be boundary nodes.
885 bool is_boundary_edge = false;
886 if (this_edge.first < mpImmersedBoundaryMesh->GetNumNodes() &&
887 this_edge.second < mpImmersedBoundaryMesh->GetNumNodes())
888 {
889 is_boundary_edge = (mpImmersedBoundaryMesh->GetNode(this_edge.first)->IsBoundaryNode() &&
890 mpImmersedBoundaryMesh->GetNode(this_edge.second)->IsBoundaryNode() );
891 }
892 unsigned is_boundary_edge_unsigned = is_boundary_edge ? 1 : 0;
893
894 (*p_edge_file) << edge_index++ << "\t" << this_edge.first << "\t" << this_edge.second << "\t" << is_boundary_edge_unsigned << std::endl;
895 }
896 p_edge_file->close();
897
898 // Having written the mesh to file, now construct it using TrianglesMeshReader
900
901 // Nested scope so reader is destroyed before we remove the temporary files
902 {
903 TrianglesMeshReader<DIM, DIM> mesh_reader(output_dir + mesh_file_name);
904 p_mesh->ConstructFromMeshReader(mesh_reader);
905 }
906
907 // Delete the temporary files
908 output_file_handler.FindFile("").Remove();
909
910 /*
911 * The original files have been deleted, it is better if the mesh object
912 * forgets about them.
913 */
915
916 return p_mesh;
917 }
918 else
919 {
920 EXCEPTION("ImmersedBoundaryCellPopulation::GetTetrahedralMeshForPDEModifier is only implemented in 2D");
921 }
922}
923
924template <unsigned DIM>
926{
927 bool non_apoptotic_cell_present = true;
928
929 if (pdeNodeIndex < this->GetNumNodes())
930 {
931 std::set<unsigned> containing_element_indices = this->GetNode(pdeNodeIndex)->rGetContainingElementIndices();
932
933 for (auto iter = containing_element_indices.begin();
934 iter != containing_element_indices.end();
935 iter++) //LCOV_EXCL_LINE
936 {
937 if (this->GetCellUsingLocationIndex(*iter)->template HasCellProperty<ApoptoticCellProperty>() )
938 {
939 non_apoptotic_cell_present = false;
940 break;
941 }
942 }
943 }
944 else
945 {
946 /*
947 * This node of the tetrahedral finite element mesh is in the centre of
948 * the element of the immersed boundary-based cell population, so we can use an
949 * offset to compute which cell to interrogate.
950 */
951 non_apoptotic_cell_present = !(this->GetCellUsingLocationIndex(pdeNodeIndex - this->GetNumNodes())->template HasCellProperty<ApoptoticCellProperty>());
952 }
953
954 return non_apoptotic_cell_present;
955}
956
957template <unsigned DIM>
959 unsigned pdeNodeIndex,
960 std::string& rVariableName,
961 bool dirichletBoundaryConditionApplies,
962 double dirichletBoundaryValue)
963{
964 unsigned num_nodes = this->GetNumNodes();
965 double value = 0.0;
966
967 /*
968 * Cells correspond to nodes in the centre of the vertex element; nodes on
969 * vertices have averaged values from containing cells.
970 */
971 if (pdeNodeIndex >= num_nodes)
972 {
973 // Offset to relate elements in vertex mesh to nodes in tetrahedral mesh
974 assert(pdeNodeIndex-num_nodes < num_nodes);
975
976 CellPtr p_cell = this->GetCellUsingLocationIndex(pdeNodeIndex - num_nodes);
977 value = p_cell->GetCellData()->GetItem(rVariableName);
978 }
979 else
980 {
982 if (dirichletBoundaryConditionApplies)
983 {
984 // We need to impose the Dirichlet boundaries again here as not represented in cell data
985 value = dirichletBoundaryValue;
986 }
987 else
988 {
989 assert(pdeNodeIndex < num_nodes);
990 Node<DIM>* p_node = this->GetNode(pdeNodeIndex);
991
992 // Average over data from containing elements (cells)
993 std::set<unsigned> containing_elements = p_node->rGetContainingElementIndices();
994 for (auto index_iter = containing_elements.begin();
995 index_iter != containing_elements.end();
996 ++index_iter)
997 {
998 assert(*index_iter < num_nodes);
999 CellPtr p_cell = this->GetCellUsingLocationIndex(*index_iter);
1000 value += p_cell->GetCellData()->GetItem(rVariableName);
1001 }
1002 value /= containing_elements.size();
1003 }
1004 }
1005
1006 return value;
1007}
1008
1009template <unsigned DIM>
1010boost::shared_ptr<AbstractImmersedBoundaryDivisionRule<DIM> > ImmersedBoundaryCellPopulation<DIM>::GetImmersedBoundaryDivisionRule()
1011{
1012 return mpImmersedBoundaryDivisionRule;
1013}
1014
1015template <unsigned DIM>
1017 boost::shared_ptr<AbstractImmersedBoundaryDivisionRule<DIM> > pImmersedBoundaryDivisionRule)
1018{
1019 mpImmersedBoundaryDivisionRule = pImmersedBoundaryDivisionRule;
1020}
1021
1022template <unsigned DIM>
1024{
1025 return mPopulationHasActiveSources;
1026}
1027
1028template <unsigned DIM>
1030{
1031 return this->GetElementCorrespondingToCell(pCell)->IsElementOnBoundary();
1032}
1033
1034template <unsigned DIM>
1036 bool hasActiveSources)
1037{
1038 mPopulationHasActiveSources = hasActiveSources;
1039}
1040
1041template <unsigned DIM>
1043 bool outputNodeRegionsToVtk)
1044{
1045 mOutputNodeRegionToVtk = outputNodeRegionsToVtk;
1046}
1047
1048template <unsigned DIM>
1050{
1051 return 0.002;
1052}
1053
1054// Explicit instantiation
1058
1059// Serialization for Boost >= 1.36
const double DOUBLE_UNSET
Definition Exception.hpp:57
#define EXCEPTION(message)
#define NEVER_REACHED
#define EXPORT_TEMPLATE_CLASS_SAME_DIMS(CLASS)
virtual void AddCellUsingLocationIndex(unsigned index, CellPtr pCell)
virtual void OpenWritersFiles(OutputFileHandler &rOutputFileHandler)
AbstractMesh< ELEMENT_DIM, SPACE_DIM > & mrMesh
unsigned GetNumNodes() const
unsigned GetNodeGlobalIndex(unsigned localIndex) const
void SetMeshHasChangedSinceLoading()
virtual void OutputCellPopulationParameters(out_stream &rParamsFile)
void Remove() const
virtual void OpenWritersFiles(OutputFileHandler &rOutputFileHandler)
ImmersedBoundaryCellPopulation(ImmersedBoundaryMesh< DIM, DIM > &rMesh, std::vector< CellPtr > &rCells, bool deleteMesh=false, bool validate=true, const std::vector< unsigned > locationIndices=std::vector< unsigned >())
double Delta1D(double dist, double spacing)
boost::shared_ptr< AbstractImmersedBoundaryDivisionRule< DIM > > mpImmersedBoundaryDivisionRule
virtual void AcceptCellWriter(boost::shared_ptr< AbstractCellWriter< DIM, DIM > > pCellWriter, CellPtr pCell)
virtual double GetCellDataItemAtPdeNode(unsigned pdeNodeIndex, std::string &rVariableName, bool dirichletBoundaryConditionApplies=false, double dirichletBoundaryValue=0.0)
void OutputCellPopulationParameters(out_stream &rParamsFile)
virtual void AcceptPopulationEventWriter(boost::shared_ptr< AbstractCellPopulationEventWriter< DIM, DIM > > pPopulationEventWriter)
ImmersedBoundaryMesh< DIM, DIM > & rGetMesh()
boost::shared_ptr< AbstractImmersedBoundaryDivisionRule< DIM > > GetImmersedBoundaryDivisionRule()
ImmersedBoundaryElement< DIM - 1, DIM > * GetLamina(unsigned laminaIndex)
ImmersedBoundaryElement< DIM, DIM > * GetElementCorrespondingToCell(CellPtr pCell)
ImmersedBoundaryElement< DIM, DIM > * GetElement(unsigned elementIndex)
std::set< unsigned > GetNeighbouringNodeIndices(unsigned index)
c_vector< double, DIM > GetLocationOfCellCentre(CellPtr pCell)
void Update(bool hasHadBirthsOrDeaths=true)
virtual void AcceptPopulationWriter(boost::shared_ptr< AbstractCellPopulationWriter< DIM, DIM > > pPopulationWriter)
virtual void AcceptPopulationCountWriter(boost::shared_ptr< AbstractCellPopulationCountWriter< DIM, DIM > > pPopulationCountWriter)
virtual bool IsPdeNodeAssociatedWithNonApoptoticCell(unsigned pdeNodeIndex)
void SetImmersedBoundaryDivisionRule(boost::shared_ptr< AbstractImmersedBoundaryDivisionRule< DIM > > pImmersedBoundaryDivisionRule)
void SetOutputNodeRegionToVtk(bool outputNodeRegionsToVtk)
CellPtr AddCell(CellPtr pNewCell, CellPtr pParentCell=CellPtr())
virtual void WriteVtkResultsToFile(const std::string &rDirectory)
std::set< unsigned > GetNeighbouringLocationIndices(CellPtr pCell)
void SetNode(unsigned index, ChastePoint< DIM > &rNewLocation)
virtual TetrahedralMesh< DIM, DIM > * GetTetrahedralMeshForPdeModifier()
ImmersedBoundaryMesh< DIM, DIM > * mpImmersedBoundaryMesh
void SetIfPopulationHasActiveSources(bool hasActiveSources)
double GetWidth(const unsigned &rDimension)
void FindElementOverlaps(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh)
const std::vector< std::vector< unsigned > > & rGetElementParts() const
void WriteVtkUsingMesh(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, std::string stamp="")
void AddPointData(std::string dataName, std::vector< double > dataPayload)
void AddCellData(std::string dataName, std::vector< double > dataPayload)
void SetNeighbourDist(double neighbourDist)
void SetElementDivisionSpacing(double elementDivisionSpacing)
Definition Node.hpp:59
std::set< unsigned > & rGetContainingElementIndices()
Definition Node.cpp:300
std::string GetOutputDirectoryFullPath() const
FileFinder FindFile(std::string leafName) const
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
static bool IsSequential()
static SimulationTime * Instance()
unsigned GetTimeStepsElapsed() const
void ConstructFromMeshReader(AbstractMeshReader< ELEMENT_DIM, SPACE_DIM > &rMeshReader)