Chaste Commit::f2ff7ee04e70ac9d06c57344df8d017dbb12b97b
ImmersedBoundaryMeshWriter.cpp
1/*
2
3Copyright (c) 2005-2024, 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 "ImmersedBoundaryMeshWriter.hpp"
37
40#include "Version.hpp"
41
42#include <boost/multi_array.hpp>
43
47template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
57
59// Implementation
61
62template <unsigned ELEMENT_DIM, unsigned SPACE_DIM>
64 const std::string& rBaseName,
65 const bool clearOutputDir)
66 : AbstractMeshWriter<ELEMENT_DIM, SPACE_DIM>(rDirectory, rBaseName, clearOutputDir),
67 mpMesh(nullptr),
68 mpIters(new MeshWriterIterators<ELEMENT_DIM, SPACE_DIM>)
69{
70 mpIters->pNodeIter = nullptr;
71 mpIters->pElemIter = nullptr;
72 mpIters->pLamIter = nullptr;
73
74 switch (SPACE_DIM)
75 {
76 case 2:
77 {
78 geom_point corner_0(0.0, 0.0);
79 geom_point corner_1(1.0, 0.0);
80 geom_point corner_2(0.0, 1.0);
81 geom_point corner_3(1.0, 1.0);
82
83 mBoundaryEdges[0] = geom_segment(corner_0, corner_2); // left edge
84 mBoundaryEdges[1] = geom_segment(corner_1, corner_3); // right edge
85 mBoundaryEdges[2] = geom_segment(corner_0, corner_1); // bottom edge
86 mBoundaryEdges[3] = geom_segment(corner_2, corner_3); // top edge
87
88 break;
89 }
90
91 default:
93 }
94
95#ifdef CHASTE_VTK
96 // Dubious, since we shouldn't yet know what any details of the mesh are.
97 mpVtkUnstructedMesh = vtkUnstructuredGrid::New();
98#endif //CHASTE_VTK
99}
100
101template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
103{
104 if (mpIters->pNodeIter)
105 {
106 delete mpIters->pNodeIter;
107 delete mpIters->pElemIter;
108 delete mpIters->pLamIter;
109 }
110
111 delete mpIters;
112
113#ifdef CHASTE_VTK
114// Dubious, since we shouldn't yet know what any details of the mesh are.
115 mpVtkUnstructedMesh->Delete(); // Reference counted
116#endif //CHASTE_VTK
117}
118
119template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
121{
122 if (mpMesh)
123 {
124 // Sanity check
125 assert(this->mNumNodes == mpMesh->GetNumNodes());
126
127 std::vector<double> coordinates(SPACE_DIM+1);
128
129 // Get the node coordinates using the node iterator (thus skipping deleted nodes)
130 for (unsigned j=0; j<SPACE_DIM; j++)
131 {
132 coordinates[j] = (*(mpIters->pNodeIter))->GetPoint()[j];
133 }
134 coordinates[SPACE_DIM] = (*(mpIters->pNodeIter))->IsBoundaryNode();
135
136 ++(*(mpIters->pNodeIter));
137
138 return coordinates;
139 }
140 else
141 {
142 return AbstractMeshWriter<ELEMENT_DIM,SPACE_DIM>::GetNextNode(); //LCOV_EXCL_LINE fairly sure this is unreachable
143 }
144}
145
146template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
148{
149 // Can only be constructed in 2D currently
150 //LCOV_EXCL_START
151 if constexpr (SPACE_DIM != 2) {
152 EXCEPTION("ImmersedBoundaryMeshWriter::GetNextImmersedBoundaryElement is not yet implemetned in 3D!");
153 }
154 //LCOV_EXCL_STOP
155
156 assert(this->mNumElements == mpMesh->GetNumElements());
157
159 elem_data.NodeIndices.resize((*(mpIters->pElemIter))->GetNumNodes());
160 for (unsigned j=0; j<elem_data.NodeIndices.size(); j++)
161 {
162 elem_data.NodeIndices[j] = (*(mpIters->pElemIter))->GetNodeGlobalIndex(j);
163 }
164
165 // Set attribute
166 elem_data.AttributeValue = (*(mpIters->pElemIter))->GetAttribute();
167
168 // Immersed boundary specific data
169 auto& element = *(*mpIters->pElemIter);
170
171 if (element.GetFluidSource() != nullptr) {
172 elem_data.hasFluidSource = true;
173 elem_data.fluidSourceIndex = element.GetFluidSource()->GetIndex();
174 } else {
175 elem_data.hasFluidSource = false;
176 }
177
178 for (auto cornerNode : element.rGetCornerNodes()) {
179 elem_data.cornerNodeIndices.push_back(cornerNode->GetIndex());
180 }
181
182 elem_data.averageNodeSpacing = element.GetAverageNodeSpacing();
183
184 elem_data.isBoundaryElement = element.IsElementOnBoundary();
185
186 ++(*(mpIters->pElemIter));
187
188 return elem_data;
189} // LCOV_EXCL_LINE
190
191template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
193{
194 //LCOV_EXCL_START
195 if constexpr (SPACE_DIM != 2) {
196 EXCEPTION("ImmersedBoundaryMeshWriter::GetNextImmersedBoundaryLamina is not yet implemetned in 3D!");
197 }
198 //LCOV_EXCL_STOP
199
200 assert(mNumLaminas == mpMesh->GetNumLaminas());
201
202 ImmersedBoundaryElementData lamina_data;
203 lamina_data.NodeIndices.resize((*(mpIters->pLamIter))->GetNumNodes());
204 for (unsigned j=0; j<lamina_data.NodeIndices.size(); j++)
205 {
206 lamina_data.NodeIndices[j] = (*(mpIters->pLamIter))->GetNodeGlobalIndex(j);
207 }
208
209 // Set attribute
210 lamina_data.AttributeValue = (*(mpIters->pLamIter))->GetAttribute();
211
212 // Immersed boundary specific data
213 auto& lamina = *(*mpIters->pLamIter);
214
215 try {
216 lamina.GetFluidSource();
217 // Can't really support 2D laminas at the moment
218 //LCOV_EXCL_START
219 if (lamina.GetFluidSource() != nullptr) {
220 lamina_data.hasFluidSource = true;
221 lamina_data.fluidSourceIndex = lamina.GetFluidSource()->GetIndex();
222 } else {
223 lamina_data.hasFluidSource = false;
224 }
225 //LCOV_EXCL_STOP
226 } catch(Exception& e) {
227 lamina_data.hasFluidSource = false;
228 }
229
230 for (auto cornerNode : lamina.rGetCornerNodes()) {
231 lamina_data.cornerNodeIndices.push_back(cornerNode->GetIndex()); // LCOV_EXCL_LINE
232 }
233
234 lamina_data.averageNodeSpacing = lamina.GetAverageNodeSpacing();
235
236 lamina_data.isBoundaryElement = lamina.IsElementOnBoundary();
237
238 ++(*(mpIters->pLamIter));
239
240 return lamina_data;
241} // LCOV_EXCL_LINE
242
243template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
245{
246#ifdef CHASTE_VTK
247 if constexpr (SPACE_DIM == 2)
248 {
249 // Create VTK mesh
250 MakeVtkMesh(rMesh);
251 // Now write VTK mesh to file
252 //assert(mpVtkUnstructedMesh->CheckAttributes() == 0);
253 vtkXMLUnstructuredGridWriter* p_writer = vtkXMLUnstructuredGridWriter::New();
254#if VTK_MAJOR_VERSION >= 6
255 p_writer->SetInputData(mpVtkUnstructedMesh);
256#else
257 p_writer->SetInput(mpVtkUnstructedMesh);
258#endif
259 // Uninitialised stuff arises (see #1079), but you can remove valgrind problems by removing compression:
260 // **** REMOVE WITH CAUTION *****
261 p_writer->SetCompressor(NULL);
262 // **** REMOVE WITH CAUTION *****
263
264 std::string vtk_file_name = this->mpOutputFileHandler->GetOutputDirectoryFullPath() + this->mBaseName;
265 if (stamp != "")
266 {
267 vtk_file_name += "_" + stamp;
268 }
269 vtk_file_name += ".vtu";
270
271 p_writer->SetFileName(vtk_file_name.c_str());
272 p_writer->Write();
273 p_writer->Delete(); // Reference counted
274 }
275 else
276 {
278 }
279#endif //CHASTE_VTK
280}
281
282template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
284{
285#ifdef CHASTE_VTK
299 // Assert mesh is 2D to simplify vtk creation
300 if constexpr (SPACE_DIM == 2)
301 {
302 FindElementOverlaps(rMesh);
303
304 // Make the Vtk mesh
305 vtkPoints* p_pts = vtkPoints::New(VTK_DOUBLE);
306 p_pts->GetData()->SetName("Vertex positions");
307
308 // Keep a track of the number of extra points that have been added to the purpose of visualisation
309 unsigned num_pts_added = 0;
310
311 // Next, we decide how to output the VTK data for elements depending on the type of overlap
312 for (auto iter = rMesh.GetElementIteratorBegin(); iter != rMesh.GetElementIteratorEnd(); ++iter)
313 {
314 unsigned elem_idx = iter->GetIndex();
315 unsigned num_nodes = iter->GetNumNodes();
316
317 // Case 1: no overlap
318 if (mElementParts.size() == 0 || mElementParts[elem_idx].empty())
319 {
320 vtkCell* p_cell = vtkPolygon::New();
321 vtkIdList* p_cell_id_list = p_cell->GetPointIds();
322 p_cell_id_list->SetNumberOfIds(num_nodes);
323
324 for (unsigned node_local_idx = 0; node_local_idx < num_nodes; ++node_local_idx)
325 {
326 // Get node, index and location
327 unsigned global_idx = iter->GetNode(node_local_idx)->GetIndex();
328 const auto& r_location = iter->GetNode(node_local_idx)->rGetLocation();
329
330 p_pts->InsertPoint(global_idx, r_location[0], r_location[1], 0.0);
331 p_cell_id_list->SetId(node_local_idx, global_idx);
332 }
333
334 mpVtkUnstructedMesh->InsertNextCell(p_cell->GetCellType(), p_cell_id_list);
335 p_cell->Delete(); // Reference counted
336 }
337 else // Case 2: at least one overlap
338 {
339 // Get the node index at the start of each part
340 const std::vector<unsigned>& start_idx_each_part = mElementParts[iter->GetIndex()];
341
342 // Get the number of parts, and put the first index onto the back of the vector for convenience
343 const auto num_parts = start_idx_each_part.size();
344
345 for (unsigned part = 0; part < num_parts; ++part)
346 {
347 const long this_start = start_idx_each_part[part];
348 const long next_start = start_idx_each_part[AdvanceMod(part, 1, num_parts)];
349
350 const long num_nodes_this_part = next_start > this_start ? next_start - this_start : num_nodes + next_start - this_start;
351
352 // Identify the extra points that need to be added
353 std::vector<c_vector<double, SPACE_DIM>> extra_locations;
354
355 // Start of the part
356 {
357 const auto& r_start_pos = iter->GetNode(this_start)->rGetLocation();
358 const auto& r_end_pos = iter->GetNode(AdvanceMod(this_start, -1, num_nodes))->rGetLocation();
359
360 const c_vector<double, SPACE_DIM> vec_a2b = rMesh.GetVectorFromAtoB(r_start_pos, r_end_pos);
361 extra_locations.emplace_back(GetIntersectionOfEdgeWithBoundary(r_start_pos, r_start_pos + vec_a2b));
362 }
363
364 // End of the part
365 {
366 const auto& r_start_pos = iter->GetNode(AdvanceMod(next_start, -1, num_nodes))->rGetLocation();
367 const auto& r_end_pos = iter->GetNode(next_start)->rGetLocation();
368
369 const c_vector<double, SPACE_DIM> vec_a2b = rMesh.GetVectorFromAtoB(r_start_pos, r_end_pos);
370 extra_locations.emplace_back(GetIntersectionOfEdgeWithBoundary(r_start_pos, r_start_pos + vec_a2b));
371 }
372
373 // If the two additional points are not on the same edge of the boundary, we also need to add a corner
374 if (std::fabs(extra_locations.front()[0] - extra_locations.back()[0]) > DBL_EPSILON &&
375 std::fabs(extra_locations.front()[1] - extra_locations.back()[1]) > DBL_EPSILON)
376 {
377 extra_locations.emplace_back(GetNearestCorner(extra_locations.front(), extra_locations.back()));
378 }
379
380 vtkCell* p_cell = vtkPolygon::New();
381 vtkIdList* p_cell_id_list = p_cell->GetPointIds();
382 p_cell_id_list->SetNumberOfIds(num_nodes_this_part + extra_locations.size());
383
384 for (long idx = 0; idx < num_nodes_this_part; ++idx)
385 {
386 unsigned node_idx = AdvanceMod(idx, this_start, num_nodes);
387
388 // Get index and location
389 unsigned global_idx = iter->GetNode(node_idx)->GetIndex();
390 const auto& r_location = iter->GetNode(node_idx)->rGetLocation();
391
392 p_pts->InsertPoint(global_idx, r_location[0], r_location[1], 0.0);
393 p_cell_id_list->SetId(idx, global_idx);
394 }
395
396 // Now add the extra locations
397 if (extra_locations.size() == 2)
398 {
399 const unsigned global_index = rMesh.GetNumNodes() + num_pts_added;
400
401 p_pts->InsertPoint(global_index, extra_locations[1][0], extra_locations[1][1], 0.0); // end
402 p_pts->InsertPoint(global_index + 1, extra_locations[0][0], extra_locations[0][1], 0.0); // start
403
404 p_cell_id_list->SetId(num_nodes_this_part, global_index);
405 p_cell_id_list->SetId(num_nodes_this_part + 1, global_index + 1);
406
407 num_pts_added += 2;
408 }
409 else if (extra_locations.size() == 3)
410 {
411 const unsigned global_index = rMesh.GetNumNodes() + num_pts_added;
412
413 p_pts->InsertPoint(global_index, extra_locations[1][0], extra_locations[1][1], 0.0); // end
414 p_pts->InsertPoint(global_index + 1, extra_locations[2][0], extra_locations[2][1], 0.0); // corner
415 p_pts->InsertPoint(global_index + 2, extra_locations[0][0], extra_locations[0][1], 0.0); // start
416
417 p_cell_id_list->SetId(num_nodes_this_part, global_index);
418 p_cell_id_list->SetId(num_nodes_this_part + 1, global_index + 1);
419 p_cell_id_list->SetId(num_nodes_this_part + 2, global_index + 2);
420
421 num_pts_added += 3;
422 }
423 else
424 {
426 }
427
428 mpVtkUnstructedMesh->InsertNextCell(p_cell->GetCellType(), p_cell_id_list);
429 p_cell->Delete(); // Reference counted
430 }
431 }
432 }
433
434 // Finally, output the VTK data for laminas
436 iter != rMesh.GetLaminaIteratorEnd();
437 ++iter)
438 {
439 unsigned num_nodes = iter->GetNumNodes();
440
441 vtkCell* p_cell = vtkPolygon::New();
442 vtkIdList* p_cell_id_list = p_cell->GetPointIds();
443 p_cell_id_list->SetNumberOfIds(num_nodes);
444
445 for (unsigned node_idx = 0; node_idx < num_nodes; node_idx++)
446 {
447 // Get node, index and location
448 Node<SPACE_DIM>* p_node = iter->GetNode(node_idx);
449 unsigned global_idx = p_node->GetIndex();
450 c_vector<double, SPACE_DIM> position = p_node->rGetLocation();
451
452 p_pts->InsertPoint(global_idx, position[0], position[1], 0.0);
453
454 p_cell_id_list->SetId(node_idx, global_idx);
455 }
456
457 mpVtkUnstructedMesh->InsertNextCell(3, p_cell_id_list);
458 p_cell->Delete(); // Reference counted
459 }
460
461 mpVtkUnstructedMesh->SetPoints(p_pts);
462 p_pts->Delete(); // Reference counted
463 }
464 else
465 {
467 }
468#endif //CHASTE_VTK
469}
470
471//LCOV_EXCL_START
476template <>
480//LCOV_EXCL_STOP
481
482template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
483void ImmersedBoundaryMeshWriter<ELEMENT_DIM, SPACE_DIM>::AddCellData(std::string dataName, std::vector<double> dataPayload)
484{
485#ifdef CHASTE_VTK
486 vtkDoubleArray* p_scalars = vtkDoubleArray::New();
487 p_scalars->SetName(dataName.c_str());
488 for (unsigned i=0; i<dataPayload.size(); i++)
489 {
490 p_scalars->InsertNextValue(dataPayload[i]);
491 }
492
493 vtkCellData* p_cell_data = mpVtkUnstructedMesh->GetCellData();
494 p_cell_data->AddArray(p_scalars);
495 p_scalars->Delete(); // Reference counted
496#endif //CHASTE_VTK
497}
498
499template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
500void ImmersedBoundaryMeshWriter<ELEMENT_DIM, SPACE_DIM>::AddPointData(std::string dataName, std::vector<double> dataPayload)
501{
502#ifdef CHASTE_VTK
503 vtkDoubleArray* p_scalars = vtkDoubleArray::New();
504 p_scalars->SetName(dataName.c_str());
505 for (double scalar : dataPayload)
506 {
507 p_scalars->InsertNextValue(scalar);
508 }
509
510 vtkPointData* p_point_data = mpVtkUnstructedMesh->GetPointData();
511 p_point_data->AddArray(p_scalars);
512 p_scalars->Delete(); // Reference counted
513#endif //CHASTE_VTK
514}
515
516template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
518{
519 this->mpMeshReader = nullptr;
520 mpMesh = &rMesh;
521
522 this->mNumNodes = mpMesh->GetNumNodes();
523 this->mNumElements = mpMesh->GetNumElements();
524 mNumLaminas = mpMesh->GetNumLaminas();
525
526 typedef typename AbstractMesh<ELEMENT_DIM,SPACE_DIM>::NodeIterator NodeIterType;
527 mpIters->pNodeIter = new NodeIterType(mpMesh->GetNodeIteratorBegin());
528
530 mpIters->pElemIter = new ElemIterType(mpMesh->GetElementIteratorBegin());
531
533 mpIters->pLamIter = new LamIterType(mpMesh->GetLaminaIteratorBegin());
534
535 WriteFiles();
536}
537
538template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
540{
541 std::string comment = "# " + ChasteBuildInfo::GetProvenanceString();
542
543 // Write node file
544 std::string node_file_name = this->mBaseName + ".node";
545 out_stream p_node_file = this->mpOutputFileHandler->OpenOutputFile(node_file_name);
546
547 // Write the node header
548 unsigned num_attr = 0;
549 unsigned max_bdy_marker = 1; // as we include boundary node information in the node file
550 unsigned num_nodes = this->GetNumNodes();
551
552 *p_node_file << num_nodes << "\t";
553 *p_node_file << SPACE_DIM << "\t";
554 *p_node_file << num_attr << "\t";
555 *p_node_file << mpMesh->GetCharacteristicNodeSpacing() << "\t";
556 *p_node_file << max_bdy_marker << "\n";
557 *p_node_file << std::setprecision(6);
558
559 // Write each node's data
560 for (unsigned item_num=0; item_num<num_nodes; item_num++)
561 {
562 std::vector<double> current_item = this->GetNextNode();
563 *p_node_file << item_num;
564 for (unsigned i=0; i<SPACE_DIM+1; i++)
565 {
566 *p_node_file << "\t" << current_item[i];
567 }
568 *p_node_file << "\n";
569 }
570 *p_node_file << comment << "\n";
571 p_node_file->close();
572
573 // Write element file
574 std::string element_file_name = this->mBaseName + ".elem";
575 out_stream p_element_file = this->mpOutputFileHandler->OpenOutputFile(element_file_name);
576
577 // Write the element header
578 num_attr = 1; //Always write element attributes
579 unsigned num_elements = this->GetNumElements();
580 *p_element_file << num_elements << "\t" << num_attr << "\n";
581
582 // Write each element's data
583 for (unsigned item_num=0; item_num<num_elements; item_num++)
584 {
585 if (SPACE_DIM == 2) // In 2D, write the node indices owned by this element
586 {
587 // Get data for this element
588 ImmersedBoundaryElementData elem_data = this->GetNextImmersedBoundaryElement();
589
590 // Get the node indices owned by this element
591 std::vector<unsigned> node_indices = elem_data.NodeIndices;
592
593 // Write this element's index and the number of nodes owned by it to file
594 *p_element_file << item_num << "\t" << node_indices.size();
595
596 // Write the node indices owned by this element to file
597 for (unsigned node_index : node_indices)
598 {
599 *p_element_file << "\t" << node_index;
600 }
601
602 *p_element_file << "\t" << elem_data.AttributeValue;
603
604 // Has fluid source
605 *p_element_file << "\t" << elem_data.hasFluidSource;
606
607 // Fluid source index
608 if (elem_data.hasFluidSource) {
609 *p_element_file << "\t" << elem_data.fluidSourceIndex;
610 }
611
612 // Corner node indices
613 *p_element_file << "\t" << elem_data.cornerNodeIndices.size();
614
615 for (auto nodeIndex : elem_data.cornerNodeIndices) {
616 *p_element_file << "\t" << nodeIndex;
617 }
618
619 // Average node spacing
620 *p_element_file << "\t" << elem_data.averageNodeSpacing;
621
622 // Is boundary element
623 *p_element_file << "\t" << elem_data.isBoundaryElement;
624
625 // New line
626 *p_element_file << "\n";
627 }
628 else // 3D
629 {
630 }
631 }
632 *p_element_file << comment << "\n";
633 p_element_file->close();
634
635 // Write lamina file
636 std::string lamina_file_name = this->mBaseName + ".lam";
637 out_stream p_lamina_file = this->mpOutputFileHandler->OpenOutputFile(lamina_file_name);
638
639 // Write the lamina header
640 num_attr = 1; //Always write element attributes
641 unsigned num_laminas = mpMesh->GetNumLaminas();
642 *p_lamina_file << num_laminas << "\t" << num_attr << "\n";
643
644 // Write each lamina's data
645 for (unsigned item_num=0; item_num<num_laminas; item_num++)
646 {
647 if (SPACE_DIM == 2) // In 2D, write the node indices owned by this element
648 {
649 // Get data for this element
650 ImmersedBoundaryElementData lamina_data = this->GetNextImmersedBoundaryLamina();
651
652 // Get the node indices owned by this element
653 std::vector<unsigned> node_indices = lamina_data.NodeIndices;
654
655 // Write this element's index and the number of nodes owned by it to file
656 *p_lamina_file << item_num << "\t" << node_indices.size();
657
658 // Write the node indices owned by this element to file
659 for (unsigned i=0; i<node_indices.size(); i++)
660 {
661 *p_lamina_file << "\t" << node_indices[i];
662 }
663
664 *p_lamina_file << "\t" << lamina_data.AttributeValue;
665
666 // Has fluid source
667 *p_lamina_file << "\t" << lamina_data.hasFluidSource;
668
669 // Fluid source index
670 if (lamina_data.hasFluidSource) {
671 *p_lamina_file << "\t" << lamina_data.fluidSourceIndex; //LCOV_EXCL_LINE
672 }
673
674 // Corner node indices
675 *p_lamina_file << "\t" << lamina_data.cornerNodeIndices.size();
676
677 for (auto nodeIndex : lamina_data.cornerNodeIndices) {
678 *p_lamina_file << "\t" << nodeIndex; //LCOV_EXCL_LINE
679 }
680
681 // Average node spacing
682 *p_lamina_file << "\t" << lamina_data.averageNodeSpacing;
683
684 // Is boundary lamina
685 *p_lamina_file << "\t" << lamina_data.isBoundaryElement;
686
687
688 // New line
689 *p_lamina_file << "\n";
690 }
691 else // 3D
692 {
693 }
694 }
695 *p_lamina_file << comment << "\n";
696 p_lamina_file->close();
697
698 // Write grid file
699 std::string grid_file_name = this->mBaseName + ".grid";
700 out_stream p_grid_file = this->mpOutputFileHandler->OpenOutputFile(grid_file_name);
701
702 // Write the element header
703 unsigned num_gridpts_x = mpMesh->GetNumGridPtsX();
704 unsigned num_gridpts_y = mpMesh->GetNumGridPtsY();
705
706 *p_grid_file << num_gridpts_x << "\t" << num_gridpts_y << "\n";
707
708 // Write grid data
709 const multi_array<double, 3>& vel_grids = mpMesh->rGet2dVelocityGrids();
710
711 for (unsigned y_idx = 0; y_idx < num_gridpts_y; y_idx ++)
712 {
713 for (unsigned x_idx = 0; x_idx < num_gridpts_x; x_idx ++)
714 {
715 *p_grid_file << vel_grids[0][x_idx][y_idx] << "\t";
716 }
717 *p_grid_file << "\n";
718 }
719
720 for (unsigned y_idx = 0; y_idx < num_gridpts_y; y_idx ++)
721 {
722 for (unsigned x_idx = 0; x_idx < num_gridpts_x; x_idx ++)
723 {
724 *p_grid_file << vel_grids[1][x_idx][y_idx] << "\t";
725 }
726 *p_grid_file << "\n";
727 }
728
729 *p_grid_file << comment << "\n";
730 p_grid_file->close();
731}
732
733template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
735{
736 if constexpr (SPACE_DIM == 2)
737 {
738 // Resize and initialise the vector of overlaps (bools; whether each element has an overlap)
739 mElementParts.resize(rMesh.GetNumAllElements());
740 for (auto& parts : mElementParts)
741 {
742 parts.clear();
743 }
744
745 // We loop over each element and the node index at each discontinuity due to periodic boundaries
746 for (auto iter = rMesh.GetElementIteratorBegin(); iter != rMesh.GetElementIteratorEnd(); ++iter)
747 {
748 for (unsigned node_idx = 0; node_idx < iter->GetNumNodes(); ++node_idx)
749 {
750 const unsigned prev_idx = AdvanceMod(node_idx, -1, iter->GetNumNodes());
751
752 const auto& this_location = iter->GetNode(node_idx)->rGetLocation();
753 const auto& prev_location = iter->GetNode(prev_idx)->rGetLocation();
754
755 if (norm_inf(this_location - prev_location) > 0.5)
756 {
757 mElementParts[iter->GetIndex()].emplace_back(node_idx);
758 }
759 }
760 }
761 }
762 else
763 {
765 }
766}
767
768template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
770 const c_vector<double, SPACE_DIM>& rStart,
771 const c_vector<double, SPACE_DIM>& rEnd)
772{
773 // Note that this function relies on SPACE_DIM == 2
774
775 // Turn the c_vector start and end into a boost::geometry segment object
776 geom_point start(rStart[0], rStart[1]);
777 geom_point end(rEnd[0], rEnd[1]);
778 geom_segment edge(start, end);
779
780 // Identify which boundary edge the intersection is with
781 std::vector<geom_point> intersections;
782 for (const auto& boundary_edge : mBoundaryEdges)
783 {
784 if (boost::geometry::intersects(edge, boundary_edge))
785 {
786 boost::geometry::intersection(edge, boundary_edge, intersections);
787 break;
788 }
789 }
790
791 // There should be exactly one intersection
792 if (intersections.size() != 1)
793 {
795 }
796
797 return Create_c_vector(intersections.front().get<0>(), intersections.front().get<1>());
798}
799
800template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
802 const c_vector<double, SPACE_DIM>& rA, const c_vector<double, SPACE_DIM>& rB) const
803{
804 // Identify the nearest corner to the average location of the two vectors
805 const double x = 0.5 * (rA[0] + rB[0]) < 0.5 ? 0.0 : 1.0;
806 const double y = 0.5 * (rA[1] + rB[1]) < 0.5 ? 0.0 : 1.0;
807
808 return Create_c_vector(x, y);
809}
810
811template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
812const std::vector<std::vector<unsigned>>& ImmersedBoundaryMeshWriter<ELEMENT_DIM, SPACE_DIM>::rGetElementParts() const
813{
814 return mElementParts;
815}
816
817// Explicit instantiation
#define EXCEPTION(message)
#define NEVER_REACHED
virtual std::vector< double > GetNextNode()
static std::string GetProvenanceString()
ImmersedBoundaryMeshWriter(const std::string &rDirectory, const std::string &rBaseName, bool clearOutputDir=true)
boost::geometry::model::point< double, 2, boost::geometry::cs::cartesian > geom_point
void FindElementOverlaps(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh)
ImmersedBoundaryElementData GetNextImmersedBoundaryElement()
c_vector< double, SPACE_DIM > GetNearestCorner(const c_vector< double, SPACE_DIM > &rA, const c_vector< double, SPACE_DIM > &rB) const
ImmersedBoundaryElementData GetNextImmersedBoundaryLamina()
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 MakeVtkMesh(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh)
std::array< geom_segment, 4 > mBoundaryEdges
void WriteFilesUsingMesh(ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM > &rMesh)
boost::geometry::model::segment< geom_point > geom_segment
c_vector< double, SPACE_DIM > GetIntersectionOfEdgeWithBoundary(const c_vector< double, SPACE_DIM > &rStart, const c_vector< double, SPACE_DIM > &rEnd)
void AddCellData(std::string dataName, std::vector< double > dataPayload)
MeshWriterIterators< ELEMENT_DIM, SPACE_DIM > * mpIters
ImmersedBoundaryElementIterator GetElementIteratorEnd()
ImmersedBoundaryLaminaIterator GetLaminaIteratorBegin(bool skipDeletedLaminas=true)
ImmersedBoundaryElementIterator GetElementIteratorBegin(bool skipDeletedElements=true)
ImmersedBoundaryLaminaIterator GetLaminaIteratorEnd()
virtual unsigned GetNumNodes() const
c_vector< double, SPACE_DIM > GetVectorFromAtoB(const c_vector< double, SPACE_DIM > &rLocation1, const c_vector< double, SPACE_DIM > &rLocation2)
unsigned GetNumAllElements() const
Definition Node.hpp:59
const c_vector< double, SPACE_DIM > & rGetLocation() const
Definition Node.cpp:139
unsigned GetIndex() const
Definition Node.cpp:158
ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM >::ImmersedBoundaryElementIterator * pElemIter
ImmersedBoundaryMesh< ELEMENT_DIM, SPACE_DIM >::ImmersedBoundaryLaminaIterator * pLamIter
AbstractMesh< ELEMENT_DIM, SPACE_DIM >::NodeIterator * pNodeIter