Chaste Commit::9e4a273f0754a391514ab12ed9d4a38fc9933db2
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#ifdef CHASTE_VTK
591 // Create mesh writer for VTK output
592 ImmersedBoundaryMeshWriter<DIM, DIM> mesh_writer(rDirectory, "results", false);
593
594 // Find the cell overlap information, and get the number of cell parts needed for each element
595 mesh_writer.FindElementOverlaps(*mpImmersedBoundaryMesh);
596 const std::vector<std::vector<unsigned>>& r_elem_parts = mesh_writer.rGetElementParts();
597
598 // Iterate over any cell writers that are present
599 for (auto cell_writer_iter = this->mCellWriters.begin();
600 cell_writer_iter != this->mCellWriters.end();
601 ++cell_writer_iter)
602 {
603 // Create vector to store VTK cell data
604 std::vector<double> vtk_cell_data;
605
606 // Iterate over immersed boundary elements
607 for (auto elem_iter = mpImmersedBoundaryMesh->GetElementIteratorBegin();
608 elem_iter != mpImmersedBoundaryMesh->GetElementIteratorEnd();
609 ++elem_iter)
610 {
611 /*
612 * Get index of this element in the mesh, and the number of parts it
613 * is broken into for visualisation.
614 */
615 const unsigned elem_index = elem_iter->GetIndex();
616 const auto num_elem_parts = r_elem_parts[elem_index].empty() ? 1 : r_elem_parts[elem_index].size();
617
618 // Get the cell corresponding to this element
619 CellPtr p_cell = this->GetCellUsingLocationIndex(elem_index);
620 assert(p_cell);
621
622 /*
623 * Populate the vector of VTK cell data. We loop over the number of
624 * output cells as this takes into account that some elements will
625 * be broken into pieces for visualisation.
626 */
627 for (unsigned elem_part = 0; elem_part < num_elem_parts; ++elem_part)
628 {
629 vtk_cell_data.push_back((*cell_writer_iter)->GetCellDataForVtkOutput(p_cell, this));
630 }
631 }
632
633 /*
634 * Iterate over immersed boundary laminas (no associated cell) to ensure
635 * vtk_cell_data is the correct size.
636 */
637 for (auto lam_iter = mpImmersedBoundaryMesh->GetLaminaIteratorBegin();
638 lam_iter != mpImmersedBoundaryMesh->GetLaminaIteratorEnd();
639 ++lam_iter)
640 {
641 vtk_cell_data.push_back(-1.0);
642 }
643
644 mesh_writer.AddCellData((*cell_writer_iter)->GetVtkCellDataName(), vtk_cell_data);
645 }
646
647 /*
648 * When outputting any CellData, we assume that the first cell is
649 * representative of all cells.
650 */
651 const unsigned num_cell_data_items = this->Begin()->GetCellData()->GetNumItems();
652 std::vector<std::string> cell_data_names = this->Begin()->GetCellData()->GetKeys();
653
654 std::vector<std::vector<double>> cell_data;
655 for (unsigned var = 0; var < num_cell_data_items; ++var)
656 {
657 std::vector<double> cell_data_var;
658 cell_data.push_back(cell_data_var);
659 }
660
661 // Iterate over immersed boundary elements
662 for (auto elem_iter = mpImmersedBoundaryMesh->GetElementIteratorBegin();
663 elem_iter != mpImmersedBoundaryMesh->GetElementIteratorEnd();
664 ++elem_iter)
665 {
666 /*
667 * Get index of this element in the mesh, and the number of parts it is
668 * broken into for visualisation.
669 */
670 const unsigned elem_index = elem_iter->GetIndex();
671 const auto num_elem_parts = r_elem_parts[elem_index].empty() ? 1 : r_elem_parts[elem_index].size();
672
673 // Get the cell corresponding to this element
674 CellPtr p_cell = this->GetCellUsingLocationIndex(elem_index);
675 assert(p_cell);
676
677 for (unsigned var = 0; var < num_cell_data_items; var++)
678 {
679 /*
680 * Populate the vector of VTK cell data. We loop over the number of
681 * output cells as this takes into account that some elements will
682 * be broken into pieces for visualisation.
683 */
684 for (unsigned elem_part = 0; elem_part < num_elem_parts; ++elem_part)
685 {
686 cell_data[var].push_back(p_cell->GetCellData()->GetItem(cell_data_names[var]));
687 }
688 }
689 }
690
691 /*
692 * Iterate over immersed boundary laminas (no associated cell) to ensure
693 * cell_data is the correct size.
694 */
695 for (auto lam_iter = mpImmersedBoundaryMesh->GetLaminaIteratorBegin();
696 lam_iter != mpImmersedBoundaryMesh->GetLaminaIteratorEnd();
697 ++lam_iter)
698 {
699 for (unsigned var = 0; var < num_cell_data_items; ++var)
700 {
701 cell_data[var].push_back(DOUBLE_UNSET);
702 }
703 }
704
705 for (unsigned var = 0; var < num_cell_data_items; ++var)
706 {
707 mesh_writer.AddCellData(cell_data_names[var], cell_data[var]);
708 }
709
710 // Write node regions
711 if (mOutputNodeRegionToVtk)
712 {
713 std::vector<double> node_regions;
714 for (auto node_iter = mpImmersedBoundaryMesh->GetNodeIteratorBegin();
715 node_iter != mpImmersedBoundaryMesh->GetNodeIteratorEnd();
716 ++node_iter)
717 {
718 node_regions.push_back(static_cast<double>(node_iter->GetRegion()));
719 }
720 mesh_writer.AddPointData("Node Regions", node_regions);
721 }
722
723 unsigned num_timesteps = SimulationTime::Instance()->GetTimeStepsElapsed();
724 std::stringstream time;
725 time << num_timesteps;
726
727 mesh_writer.WriteVtkUsingMesh(*mpImmersedBoundaryMesh, time.str());
728
729 *(this->mpVtkMetaFile) << " <DataSet timestep=\"";
730 *(this->mpVtkMetaFile) << num_timesteps;
731 *(this->mpVtkMetaFile) << "\" group=\"\" part=\"0\" file=\"results_";
732 *(this->mpVtkMetaFile) << num_timesteps;
733 *(this->mpVtkMetaFile) << ".vtu\"/>\n";
734#endif //CHASTE_VTK
735}
736
737template <unsigned DIM>
739 OutputFileHandler& rOutputFileHandler)
740{
741 if (this->mOutputResultsForChasteVisualizer)
742 {
743 if (!this->template HasWriter<CellPopulationElementWriter>())
744 {
745 this->template AddPopulationWriter<CellPopulationElementWriter>();
746 }
747 }
748
750}
751
752template <unsigned DIM>
754 out_stream& rParamsFile)
755{
756 // Add the division rule parameters
757 *rParamsFile << "\t\t<ImmersedBoundaryDivisionRule>\n";
758 mpImmersedBoundaryDivisionRule->OutputCellImmersedBoundaryDivisionRuleInfo(rParamsFile);
759 *rParamsFile << "\t\t</ImmersedBoundaryDivisionRule>\n";
760
761 // Call method on direct parent class
763}
764
765template <unsigned DIM>
766double ImmersedBoundaryCellPopulation<DIM>::GetWidth(const unsigned& rDimension)
767{
768 double width = this->mrMesh.GetWidth(rDimension);
769 return width;
770}
771
772template <unsigned DIM>
774 unsigned index)
775{
776 return mpImmersedBoundaryMesh->GetNeighbouringNodeIndices(index);
777}
778
779template <unsigned DIM>
781{
782 // This method only works in 2D sequential
783 assert(PetscTools::IsSequential());
784 if constexpr (DIM == 2)
785 {
786 unsigned num_vertex_nodes = mpImmersedBoundaryMesh->GetNumNodes();
787 unsigned num_vertex_elements = mpImmersedBoundaryMesh->GetNumElements();
788
789 std::string mesh_file_name = "mesh";
790
791 // Get a unique temporary foldername
792 std::stringstream pid;
793 pid << getpid();
794 OutputFileHandler output_file_handler("2D_temporary_tetrahedral_mesh_" + pid.str());
795 std::string output_dir = output_file_handler.GetOutputDirectoryFullPath();
796
797 // Compute the number of nodes in the TetrahedralMesh
798 unsigned num_tetrahedral_nodes = num_vertex_nodes + num_vertex_elements;
799
800 // Write node file
801 out_stream p_node_file = output_file_handler.OpenOutputFile(mesh_file_name+".node");
802 (*p_node_file) << std::scientific;
803 (*p_node_file) << std::setprecision(20);
804 (*p_node_file) << num_tetrahedral_nodes << "\t2\t0\t1" << std::endl;
805
806 // Begin by writing each node in the VertexMesh
807 auto nodes = mpImmersedBoundaryMesh->rGetNodes();
808 for (auto p_node : nodes)
809 {
810 unsigned index = p_node->GetIndex();
811 const c_vector<double, DIM>& r_location = p_node->rGetLocation();
812 unsigned is_boundary_node = p_node->IsBoundaryNode() ? 1 : 0;
813
814 (*p_node_file) << index << "\t" << r_location[0] << "\t" << r_location[1] << "\t" << is_boundary_node << std::endl;
815 }
816
817 // Now write an additional node at each ImmersedBoundaryElement's centroid
818 unsigned num_tetrahedral_elements = 0;
819 for (unsigned vertex_elem_index = 0;
820 vertex_elem_index < num_vertex_elements;
821 ++vertex_elem_index)
822 {
823 unsigned index = num_vertex_nodes + vertex_elem_index;
824
825 c_vector<double, DIM> location = mpImmersedBoundaryMesh->GetCentroidOfElement(vertex_elem_index);
826
827 // Any node located at a ImmersedBoundaryElement's centroid will not be a boundary node
828 unsigned is_boundary_node = 0;
829 (*p_node_file) << index << "\t" << location[0] << "\t" << location[1] << "\t" << is_boundary_node << std::endl;
830
831 // Also keep track of how many tetrahedral elements there will be
832 num_tetrahedral_elements += mpImmersedBoundaryMesh->GetElement(vertex_elem_index)->GetNumNodes();
833 }
834 p_node_file->close();
835
836 // Write element file
837 out_stream p_elem_file = output_file_handler.OpenOutputFile(mesh_file_name+".ele");
838 (*p_elem_file) << std::scientific;
839 (*p_elem_file) << num_tetrahedral_elements << "\t3\t0" << std::endl;
840
841 std::set<std::pair<unsigned, unsigned> > tetrahedral_edges;
842
843 unsigned tetrahedral_elem_index = 0;
844 for (unsigned vertex_elem_index = 0;
845 vertex_elem_index < num_vertex_elements;
846 ++vertex_elem_index)
847 {
848 ImmersedBoundaryElement<DIM, DIM>* p_vertex_element = mpImmersedBoundaryMesh->GetElement(vertex_elem_index);
849
850 // Iterate over nodes owned by this ImmersedBoundaryElement
851 unsigned num_nodes_in_vertex_element = p_vertex_element->GetNumNodes();
852 for (unsigned local_index = 0;
853 local_index < num_nodes_in_vertex_element;
854 ++local_index)
855 {
856 unsigned node_0_index = p_vertex_element->GetNodeGlobalIndex(local_index);
857 unsigned node_1_index = p_vertex_element->GetNodeGlobalIndex((local_index+1)%num_nodes_in_vertex_element);
858 unsigned node_2_index = num_vertex_nodes + vertex_elem_index;
859
860 (*p_elem_file) << tetrahedral_elem_index++ << "\t" << node_0_index << "\t" << node_1_index << "\t" << node_2_index << std::endl;
861
862 // Add edges to the set if they are not already present
863 std::pair<unsigned, unsigned> edge_0 = this->CreateOrderedPair(node_0_index, node_1_index);
864 std::pair<unsigned, unsigned> edge_1 = this->CreateOrderedPair(node_1_index, node_2_index);
865 std::pair<unsigned, unsigned> edge_2 = this->CreateOrderedPair(node_2_index, node_0_index);
866
867 tetrahedral_edges.insert(edge_0);
868 tetrahedral_edges.insert(edge_1);
869 tetrahedral_edges.insert(edge_2);
870 }
871 }
872 p_elem_file->close();
873
874 // Write edge file
875 out_stream p_edge_file = output_file_handler.OpenOutputFile(mesh_file_name+".edge");
876 (*p_edge_file) << std::scientific;
877 (*p_edge_file) << tetrahedral_edges.size() << "\t1" << std::endl;
878
879 unsigned edge_index = 0;
880 for (auto edge_iter = tetrahedral_edges.begin();
881 edge_iter != tetrahedral_edges.end();
882 ++edge_iter)
883 {
884 std::pair<unsigned, unsigned> this_edge = *edge_iter;
885
886 // To be a boundary edge both nodes need to be boundary nodes.
887 bool is_boundary_edge = false;
888 if (this_edge.first < mpImmersedBoundaryMesh->GetNumNodes() &&
889 this_edge.second < mpImmersedBoundaryMesh->GetNumNodes())
890 {
891 is_boundary_edge = (mpImmersedBoundaryMesh->GetNode(this_edge.first)->IsBoundaryNode() &&
892 mpImmersedBoundaryMesh->GetNode(this_edge.second)->IsBoundaryNode() );
893 }
894 unsigned is_boundary_edge_unsigned = is_boundary_edge ? 1 : 0;
895
896 (*p_edge_file) << edge_index++ << "\t" << this_edge.first << "\t" << this_edge.second << "\t" << is_boundary_edge_unsigned << std::endl;
897 }
898 p_edge_file->close();
899
900 // Having written the mesh to file, now construct it using TrianglesMeshReader
902
903 // Nested scope so reader is destroyed before we remove the temporary files
904 {
905 TrianglesMeshReader<DIM, DIM> mesh_reader(output_dir + mesh_file_name);
906 p_mesh->ConstructFromMeshReader(mesh_reader);
907 }
908
909 // Delete the temporary files
910 output_file_handler.FindFile("").Remove();
911
912 /*
913 * The original files have been deleted, it is better if the mesh object
914 * forgets about them.
915 */
917
918 return p_mesh;
919 }
920 else
921 {
922 EXCEPTION("ImmersedBoundaryCellPopulation::GetTetrahedralMeshForPDEModifier is only implemented in 2D");
923 }
924}
925
926template <unsigned DIM>
928{
929 bool non_apoptotic_cell_present = true;
930
931 if (pdeNodeIndex < this->GetNumNodes())
932 {
933 std::set<unsigned> containing_element_indices = this->GetNode(pdeNodeIndex)->rGetContainingElementIndices();
934
935 for (auto iter = containing_element_indices.begin();
936 iter != containing_element_indices.end();
937 iter++) //LCOV_EXCL_LINE
938 {
939 if (this->GetCellUsingLocationIndex(*iter)->template HasCellProperty<ApoptoticCellProperty>() )
940 {
941 non_apoptotic_cell_present = false;
942 break;
943 }
944 }
945 }
946 else
947 {
948 /*
949 * This node of the tetrahedral finite element mesh is in the centre of
950 * the element of the immersed boundary-based cell population, so we can use an
951 * offset to compute which cell to interrogate.
952 */
953 non_apoptotic_cell_present = !(this->GetCellUsingLocationIndex(pdeNodeIndex - this->GetNumNodes())->template HasCellProperty<ApoptoticCellProperty>());
954 }
955
956 return non_apoptotic_cell_present;
957}
958
959template <unsigned DIM>
961 unsigned pdeNodeIndex,
962 std::string& rVariableName,
963 bool dirichletBoundaryConditionApplies,
964 double dirichletBoundaryValue)
965{
966 unsigned num_nodes = this->GetNumNodes();
967 double value = 0.0;
968
969 /*
970 * Cells correspond to nodes in the centre of the vertex element; nodes on
971 * vertices have averaged values from containing cells.
972 */
973 if (pdeNodeIndex >= num_nodes)
974 {
975 // Offset to relate elements in vertex mesh to nodes in tetrahedral mesh
976 assert(pdeNodeIndex-num_nodes < num_nodes);
977
978 CellPtr p_cell = this->GetCellUsingLocationIndex(pdeNodeIndex - num_nodes);
979 value = p_cell->GetCellData()->GetItem(rVariableName);
980 }
981 else
982 {
984 if (dirichletBoundaryConditionApplies)
985 {
986 // We need to impose the Dirichlet boundaries again here as not represented in cell data
987 value = dirichletBoundaryValue;
988 }
989 else
990 {
991 assert(pdeNodeIndex < num_nodes);
992 Node<DIM>* p_node = this->GetNode(pdeNodeIndex);
993
994 // Average over data from containing elements (cells)
995 std::set<unsigned> containing_elements = p_node->rGetContainingElementIndices();
996 for (auto index_iter = containing_elements.begin();
997 index_iter != containing_elements.end();
998 ++index_iter)
999 {
1000 assert(*index_iter < num_nodes);
1001 CellPtr p_cell = this->GetCellUsingLocationIndex(*index_iter);
1002 value += p_cell->GetCellData()->GetItem(rVariableName);
1003 }
1004 value /= containing_elements.size();
1005 }
1006 }
1007
1008 return value;
1009}
1010
1011template <unsigned DIM>
1012boost::shared_ptr<AbstractImmersedBoundaryDivisionRule<DIM> > ImmersedBoundaryCellPopulation<DIM>::GetImmersedBoundaryDivisionRule()
1013{
1014 return mpImmersedBoundaryDivisionRule;
1015}
1016
1017template <unsigned DIM>
1019 boost::shared_ptr<AbstractImmersedBoundaryDivisionRule<DIM> > pImmersedBoundaryDivisionRule)
1020{
1021 mpImmersedBoundaryDivisionRule = pImmersedBoundaryDivisionRule;
1022}
1023
1024template <unsigned DIM>
1026{
1027 return mPopulationHasActiveSources;
1028}
1029
1030template <unsigned DIM>
1032{
1033 return this->GetElementCorrespondingToCell(pCell)->IsElementOnBoundary();
1034}
1035
1036template <unsigned DIM>
1038 bool hasActiveSources)
1039{
1040 mPopulationHasActiveSources = hasActiveSources;
1041}
1042
1043template <unsigned DIM>
1045 bool outputNodeRegionsToVtk)
1046{
1047 mOutputNodeRegionToVtk = outputNodeRegionsToVtk;
1048}
1049
1050template <unsigned DIM>
1052{
1053 return 0.002;
1054}
1055
1056// Explicit instantiation
1060
1061// 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)