Chaste Commit::6e4f5fe395bca70eb7641cf6e0e87f450383ca5a
AbstractCardiacProblem.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#include "AbstractCardiacProblem.hpp"
36
37#include "DistributedVector.hpp"
38#include "Exception.hpp"
39#include "GenericMeshReader.hpp"
40#include "Hdf5ToCmguiConverter.hpp"
41#include "Hdf5ToMeshalyzerConverter.hpp"
42#include "Hdf5ToVtkConverter.hpp"
43#include "HeartConfig.hpp"
44#include "HeartEventHandler.hpp"
45#include "LinearSystem.hpp"
46#include "PetscTools.hpp"
47#include "PostProcessingWriter.hpp"
48#include "ProgressReporter.hpp"
49#include "TimeStepper.hpp"
50
51template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
54 : mMeshFilename(""), // i.e. undefined
55 mAllocatedMemoryForMesh(false),
56 mWriteInfo(false),
57 mPrintOutput(true),
58 mpCardiacTissue(NULL),
59 mpSolver(NULL),
60 mpCellFactory(pCellFactory),
61 mpMesh(NULL),
62 mSolution(NULL),
63 mCurrentTime(0.0),
64 mpTimeAdaptivityController(NULL),
65 mpWriter(NULL),
66 mUseHdf5DataWriterCache(false),
67 mHdf5DataWriterChunkSizeAndAlignment(0)
68{
69 assert(mNodesToOutput.empty());
70 if (!mpCellFactory)
71 {
72 EXCEPTION("AbstractCardiacProblem: Please supply a cell factory pointer to your cardiac problem constructor.");
73 }
74 HeartEventHandler::BeginEvent(HeartEventHandler::EVERYTHING);
75}
76
77template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
79 // It doesn't really matter what we initialise these to, as they'll be overwritten by
80 // the serialization methods
81 : mMeshFilename(""),
82 mAllocatedMemoryForMesh(false), // Handled by AbstractCardiacTissue
83 mWriteInfo(false),
84 mPrintOutput(true),
85 mVoltageColumnId(UINT_MAX),
86 mTimeColumnId(UINT_MAX),
87 mNodeColumnId(UINT_MAX),
88 mpCardiacTissue(NULL),
89 mpSolver(NULL),
90 mpCellFactory(NULL),
91 mpMesh(NULL),
92 mSolution(NULL),
93 mCurrentTime(0.0),
94 mpTimeAdaptivityController(NULL),
95 mpWriter(NULL),
96 mUseHdf5DataWriterCache(false),
97 mHdf5DataWriterChunkSizeAndAlignment(0)
98{
99}
100
101template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
103{
104 delete mpCardiacTissue;
105 if (mSolution)
106 {
107 PetscTools::Destroy(mSolution);
108 }
109
110 if (mAllocatedMemoryForMesh)
111 {
112 delete mpMesh;
113 }
114}
115
116template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
118{
119 HeartEventHandler::BeginEvent(HeartEventHandler::READ_MESH);
120 if (mpMesh)
121 {
123 {
124 WARNING("Using a non-distributed mesh in a parallel simulation is not a good idea.");
125 }
126 }
127 else
128 {
129 // If no mesh has been passed, we get it from the configuration file
130 try
131 {
132 if (HeartConfig::Instance()->GetLoadMesh())
133 {
134 CreateMeshFromHeartConfig();
135 auto p_mesh_reader = GenericMeshReader<ELEMENT_DIM, SPACE_DIM>(HeartConfig::Instance()->GetMeshName());
136 mpMesh->ConstructFromMeshReader(*p_mesh_reader);
137 }
138 else if (HeartConfig::Instance()->GetCreateMesh())
139 {
140 CreateMeshFromHeartConfig();
141 assert(HeartConfig::Instance()->GetSpaceDimension() == SPACE_DIM);
142 double inter_node_space = HeartConfig::Instance()->GetInterNodeSpace();
143
144 switch (HeartConfig::Instance()->GetSpaceDimension())
145 {
146 case 1:
147 {
148 c_vector<double, 1> fibre_length;
149 HeartConfig::Instance()->GetFibreLength(fibre_length);
150 mpMesh->ConstructRegularSlabMesh(inter_node_space, fibre_length[0]);
151 break;
152 }
153 case 2:
154 {
155 c_vector<double, 2> sheet_dimensions; //cm
156 HeartConfig::Instance()->GetSheetDimensions(sheet_dimensions);
157 mpMesh->ConstructRegularSlabMesh(inter_node_space, sheet_dimensions[0], sheet_dimensions[1]);
158 break;
159 }
160 case 3:
161 {
162 c_vector<double, 3> slab_dimensions; //cm
163 HeartConfig::Instance()->GetSlabDimensions(slab_dimensions);
164 mpMesh->ConstructRegularSlabMesh(inter_node_space, slab_dimensions[0], slab_dimensions[1], slab_dimensions[2]);
165 break;
166 }
167 default:
169 }
170 }
171 else
172 {
174 }
175
176 mAllocatedMemoryForMesh = true;
177 }
178 catch (Exception& e)
179 {
180 EXCEPTION(std::string("No mesh given: define it in XML parameters file or call SetMesh()\n") + e.GetShortMessage());
181 }
182 }
183 mpCellFactory->SetMesh(mpMesh);
184 HeartEventHandler::EndEvent(HeartEventHandler::READ_MESH);
185
186 HeartEventHandler::BeginEvent(HeartEventHandler::INITIALISE);
187
188 // If the user requested transmural stuff, we fill in the mCellHeterogeneityAreas here
189 if (HeartConfig::Instance()->AreCellularTransmuralHeterogeneitiesRequested())
190 {
191 mpCellFactory->FillInCellularTransmuralAreas();
192 }
193
194 delete mpCardiacTissue; // In case we're called twice
195 mpCardiacTissue = CreateCardiacTissue();
196
197 HeartEventHandler::EndEvent(HeartEventHandler::INITIALISE);
198
199 // Delete any previous solution, so we get a fresh initial condition
200 if (mSolution)
201 {
202 HeartEventHandler::BeginEvent(HeartEventHandler::COMMUNICATION);
203 PetscTools::Destroy(mSolution);
204 mSolution = NULL;
205 HeartEventHandler::EndEvent(HeartEventHandler::COMMUNICATION);
206 }
207
208 // Always start at time zero
209 mCurrentTime = 0.0;
210
211 // For Bidomain with bath, this is where we set up the electrodes
212 SetElectrodes();
213}
214
215template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
220
221template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
226
227template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
229{
230 if (mpCardiacTissue == NULL) // if tissue is NULL, Initialise() probably hasn't been called
231 {
232 EXCEPTION("Cardiac tissue is null, Initialise() probably hasn't been called");
233 }
234 if (HeartConfig::Instance()->GetSimulationDuration() <= mCurrentTime)
235 {
236 EXCEPTION("End time should be in the future");
237 }
238 if (mPrintOutput)
239 {
240 if ((HeartConfig::Instance()->GetOutputDirectory() == "") || (HeartConfig::Instance()->GetOutputFilenamePrefix() == ""))
241 {
242 EXCEPTION("Either explicitly specify not to print output (call PrintOutput(false)) or specify the output directory and filename prefix");
243 }
244 }
245
246 double end_time = HeartConfig::Instance()->GetSimulationDuration();
247 double pde_time = HeartConfig::Instance()->GetPdeTimeStep();
248
249 /*
250 * MatrixIsConstant stuff requires CONSTANT dt - do some checks to make sure
251 * the TimeStepper won't find non-constant dt.
252 * Note: printing_time does not have to divide end_time, but dt must divide
253 * printing_time and end_time.
254 * HeartConfig checks pde_dt divides printing dt.
255 */
257 if (fabs(end_time - pde_time * round(end_time / pde_time)) > 1e-10)
258 {
259 EXCEPTION("PDE timestep does not seem to divide end time - check parameters");
260 }
261}
262
263template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
265{
266 DistributedVectorFactory* p_factory = mpMesh->GetDistributedVectorFactory();
267 Vec initial_condition = p_factory->CreateVec(PROBLEM_DIM);
268 DistributedVector ic = p_factory->CreateDistributedVector(initial_condition);
269 std::vector<DistributedVector::Stripe> stripe;
270 stripe.reserve(PROBLEM_DIM);
271
272 for (unsigned i = 0; i < PROBLEM_DIM; i++)
273 {
274 stripe.push_back(DistributedVector::Stripe(ic, i));
275 }
276
277 for (DistributedVector::Iterator index = ic.Begin();
278 index != ic.End();
279 ++index)
280 {
281 stripe[0][index] = mpCardiacTissue->GetCardiacCell(index.Global)->GetVoltage();
282 if (PROBLEM_DIM == 2)
283 {
284 stripe[1][index] = 0;
285 }
286 }
287
288 ic.Restore();
289
290 return initial_condition;
291}
292
293template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
295{
296 /*
297 * If this fails the mesh has already been set. We assert rather throw
298 * an exception to avoid a memory leak when checking it throws correctly.
299 */
300 assert(mpMesh == NULL);
301 assert(pMesh != NULL);
302 mAllocatedMemoryForMesh = false;
303 mpMesh = pMesh;
304}
305
306template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
308{
309 mPrintOutput = printOutput;
310}
311
312template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
314{
315 mWriteInfo = writeInfo;
316}
317
318template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
323
324template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
326{
327 return mpMesh->GetDistributedVectorFactory()->CreateDistributedVector(mSolution);
328}
329
330template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
335
336template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
342
343template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
345{
346 if (mpCardiacTissue == NULL)
347 {
348 EXCEPTION("Tissue not yet set up, you may need to call Initialise() before GetTissue().");
349 }
350 return mpCardiacTissue;
351}
352
353template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
355 bool useAdaptivity,
357{
358 if (useAdaptivity)
359 {
360 assert(pController);
361 mpTimeAdaptivityController = pController;
362 }
363 else
364 {
365 mpTimeAdaptivityController = NULL;
366 }
367}
368
369template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
371{
372 PreSolveChecks();
373
374 std::vector<double> additional_stopping_times;
375 SetUpAdditionalStoppingTimes(additional_stopping_times);
376
377 TimeStepper stepper(mCurrentTime,
378 HeartConfig::Instance()->GetSimulationDuration(),
379 HeartConfig::Instance()->GetPrintingTimeStep(),
380 false,
381 additional_stopping_times);
382 // Note that SetUpAdditionalStoppingTimes is a method from the BidomainWithBath class it adds
383 // electrode events into the regular time-stepping
384 // EXCEPTION("Electrode switch on/off events should coincide with printing time steps.");
385
386 if (!mpBoundaryConditionsContainer) // the user didn't supply a bcc
387 {
388 // Set up the default bcc
389 mpDefaultBoundaryConditionsContainer.reset(new BoundaryConditionsContainer<ELEMENT_DIM, SPACE_DIM, PROBLEM_DIM>);
390 for (unsigned problem_index = 0; problem_index < PROBLEM_DIM; problem_index++)
391 {
392 mpDefaultBoundaryConditionsContainer->DefineZeroNeumannOnMeshBoundary(mpMesh, problem_index);
393 }
394 mpBoundaryConditionsContainer = mpDefaultBoundaryConditionsContainer;
395 }
396
397 assert(mpSolver == NULL);
398 mpSolver = CreateSolver(); // passes mpBoundaryConditionsContainer to solver
399
400 // If we have already run a simulation, use the old solution as initial condition
401 Vec initial_condition;
402 if (mSolution)
403 {
404 initial_condition = mSolution;
405 }
406 else
407 {
408 initial_condition = CreateInitialCondition();
409 }
410
411 std::string progress_reporter_dir;
412
413 if (mPrintOutput)
414 {
415 HeartEventHandler::BeginEvent(HeartEventHandler::WRITE_OUTPUT);
416 bool extending_file = false;
417 try
418 {
419 extending_file = InitialiseWriter();
420 }
421 catch (Exception& e)
422 {
423 delete mpWriter;
424 mpWriter = NULL;
425 delete mpSolver;
426 if (mSolution != initial_condition)
427 {
428 /*
429 * A PETSc Vec is a pointer, so we *don't* need to free the memory if it is
430 * freed somewhere else (e.g. in the destructor). If this is a resumed solution
431 * we set initial_condition = mSolution earlier. mSolution is going to be
432 * cleaned up in the constructor. So, only PetscTools::Destroy( initial_condition ) when
433 * it is not equal to mSolution.
434 */
435 PetscTools::Destroy(initial_condition);
436 }
437 throw e;
438 }
439
440 /*
441 * If we are resuming a simulation (i.e. mSolution already exists) and
442 * we are extending a .h5 file that already exists then there is no need
443 * to write the initial condition to file - it is already there as the
444 * final solution of the previous run.
445 */
446 if (!(mSolution && extending_file))
447 {
448 WriteOneStep(stepper.GetTime(), initial_condition);
449 mpWriter->AdvanceAlongUnlimitedDimension();
450 }
451 HeartEventHandler::EndEvent(HeartEventHandler::WRITE_OUTPUT);
452
453 progress_reporter_dir = HeartConfig::Instance()->GetOutputDirectory();
454 }
455 else
456 {
457 progress_reporter_dir = ""; // progress printed to CHASTE_TEST_OUTPUT
458 }
459 for (boost::shared_ptr<AbstractOutputModifier> p_output_modifier : mOutputModifiers)
460 {
461 p_output_modifier->InitialiseAtStart(this->mpMesh->GetDistributedVectorFactory(), this->mpMesh->rGetNodePermutation());
462 p_output_modifier->ProcessSolutionAtTimeStep(stepper.GetTime(), initial_condition, PROBLEM_DIM);
463 }
464
465 /*
466 * Create a progress reporter so users can track how much has gone and
467 * estimate how much time is left. Note this has to be done after the
468 * InitialiseWriter above (if mPrintOutput==true).
469 */
470 ProgressReporter progress_reporter(progress_reporter_dir,
471 mCurrentTime,
472 HeartConfig::Instance()->GetSimulationDuration());
473 progress_reporter.Update(mCurrentTime);
474
475 mpSolver->SetTimeStep(HeartConfig::Instance()->GetPdeTimeStep());
476 if (mpTimeAdaptivityController)
477 {
478 mpSolver->SetTimeAdaptivityController(mpTimeAdaptivityController);
479 }
480
481 while (!stepper.IsTimeAtEnd())
482 {
483 // Solve from now up to the next printing time
484 mpSolver->SetTimes(stepper.GetTime(), stepper.GetNextTime());
485 mpSolver->SetInitialCondition(initial_condition);
486
487 AtBeginningOfTimestep(stepper.GetTime());
488
489 try
490 {
491 try
492 {
493 mSolution = mpSolver->Solve();
494 }
495 catch (const Exception& e)
496 {
497#ifndef NDEBUG
499#endif
500 throw e;
501 }
502#ifndef NDEBUG
504#endif
506 catch (const Exception& e)
507 {
508 // Free memory
509 delete mpSolver;
510 mpSolver = NULL;
511 if (initial_condition != mSolution)
512 {
513 /*
514 * A PETSc Vec is a pointer, so we *don't* need to free the memory if it is
515 * freed somewhere else (e.g. in the destructor). Later, in this while loop
516 * we will set initial_condition = mSolution (or, if this is a resumed solution
517 * it may also have been done when initial_condition was created). mSolution
518 * is going to be cleaned up in the destructor. So, only PetscTools::Destroy()
519 * initial_condition when it is not equal to mSolution (see #1695).
520 */
521 PetscTools::Destroy(initial_condition);
522 }
523
524 // Re-throw
526 CloseFilesAndPostProcess();
528 throw e;
529 }
530
531 // Free old initial condition
532 HeartEventHandler::BeginEvent(HeartEventHandler::COMMUNICATION);
533 PetscTools::Destroy(initial_condition);
534 HeartEventHandler::EndEvent(HeartEventHandler::COMMUNICATION);
535
536 // Initial condition for next loop is current solution
537 initial_condition = mSolution;
538
539 // Update the current time
540 stepper.AdvanceOneTimeStep();
541 mCurrentTime = stepper.GetTime();
542
543 // Print out details at current time if asked for
544 if (mWriteInfo)
545 {
546 HeartEventHandler::BeginEvent(HeartEventHandler::WRITE_OUTPUT);
547 WriteInfo(stepper.GetTime());
548 HeartEventHandler::EndEvent(HeartEventHandler::WRITE_OUTPUT);
549 }
550
551 for (boost::shared_ptr<AbstractOutputModifier> p_output_modifier : mOutputModifiers)
552 {
553 p_output_modifier->ProcessSolutionAtTimeStep(stepper.GetTime(), mSolution, PROBLEM_DIM);
554 }
555 if (mPrintOutput)
556 {
557 // Writing data out to the file <FilenamePrefix>.dat
558 HeartEventHandler::BeginEvent(HeartEventHandler::WRITE_OUTPUT);
559 WriteOneStep(stepper.GetTime(), mSolution);
560 // Just flags that we've finished a time-step; won't actually 'extend' unless new data is written.
561 mpWriter->AdvanceAlongUnlimitedDimension();
562
563 HeartEventHandler::EndEvent(HeartEventHandler::WRITE_OUTPUT);
564 }
566 progress_reporter.Update(stepper.GetTime());
567
568 OnEndOfTimestep(stepper.GetTime());
569 }
570
571 // Free solver
572 delete mpSolver;
573 mpSolver = NULL;
574
575 // Close the file that stores voltage values
576 progress_reporter.PrintFinalising();
577 for (boost::shared_ptr<AbstractOutputModifier> p_output_modifier : mOutputModifiers)
578 {
579 p_output_modifier->FinaliseAtEnd();
580 }
581 CloseFilesAndPostProcess();
582 HeartEventHandler::EndEvent(HeartEventHandler::EVERYTHING);
583}
585template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
587{
588 // Close files
589 if (!mPrintOutput)
590 {
591 // Nothing to do
592 return;
593 }
594 HeartEventHandler::BeginEvent(HeartEventHandler::WRITE_OUTPUT);
595 // If write caching is on, the next line might actually take a significant amount of time.
596 delete mpWriter;
597 mpWriter = NULL;
598 HeartEventHandler::EndEvent(HeartEventHandler::WRITE_OUTPUT);
599
600 FileFinder test_output(HeartConfig::Instance()->GetOutputDirectory(), RelativeTo::ChasteTestOutput);
602 /********************************************************************************
603 * Run all post processing.
604 *
605 * The PostProcessingWriter class examines what is requested in HeartConfig and
606 * adds the relevant data to the HDF5 file.
607 * This is converted to different visualizer formats along with the solution
608 * in the DATA_CONVERSION block below.
609 *********************************************************************************/
610
611 HeartEventHandler::BeginEvent(HeartEventHandler::POST_PROC);
612 if (HeartConfig::Instance()->IsPostProcessingRequested())
613 {
615 test_output,
616 HeartConfig::Instance()->GetOutputFilenamePrefix(),
617 "V",
618 mHdf5DataWriterChunkSizeAndAlignment);
619 post_writer.WritePostProcessingFiles();
620 }
621 HeartEventHandler::EndEvent(HeartEventHandler::POST_PROC);
622
623 /********************************************************************************************
624 * Convert HDF5 datasets (solution and postprocessing maps) to different visualizer formats
625 ********************************************************************************************/
627 HeartEventHandler::BeginEvent(HeartEventHandler::DATA_CONVERSION);
628 // Only if results files were written and we are outputting all nodes
629 if (mNodesToOutput.empty())
630 {
631 if (HeartConfig::Instance()->GetVisualizeWithMeshalyzer())
632 {
633 // Convert simulation data to Meshalyzer format
635 HeartConfig::Instance()->GetOutputFilenamePrefix(),
636 mpMesh,
637 HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering(),
638 HeartConfig::Instance()->GetVisualizerOutputPrecision());
639 std::string subdirectory_name = converter.GetSubdirectory();
640 HeartConfig::Instance()->Write(false, subdirectory_name);
642
643 if (HeartConfig::Instance()->GetVisualizeWithCmgui())
644 {
645 // Convert simulation data to Cmgui format
647 HeartConfig::Instance()->GetOutputFilenamePrefix(),
648 mpMesh,
649 GetHasBath(),
650 HeartConfig::Instance()->GetVisualizerOutputPrecision());
651 std::string subdirectory_name = converter.GetSubdirectory();
652 HeartConfig::Instance()->Write(false, subdirectory_name);
653 }
654
655 if (HeartConfig::Instance()->GetVisualizeWithVtk())
656 {
657 // Convert simulation data to VTK format
658 Hdf5ToVtkConverter<ELEMENT_DIM, SPACE_DIM> converter(test_output,
659 HeartConfig::Instance()->GetOutputFilenamePrefix(),
660 mpMesh,
661 false,
662 HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering());
663 std::string subdirectory_name = converter.GetSubdirectory();
664 HeartConfig::Instance()->Write(false, subdirectory_name);
665 }
666
667 if (HeartConfig::Instance()->GetVisualizeWithParallelVtk())
668 {
669 // Convert simulation data to parallel VTK (pvtu) format
670 Hdf5ToVtkConverter<ELEMENT_DIM, SPACE_DIM> converter(test_output,
671 HeartConfig::Instance()->GetOutputFilenamePrefix(),
672 mpMesh,
673 true,
674 HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering());
675 std::string subdirectory_name = converter.GetSubdirectory();
676 HeartConfig::Instance()->Write(false, subdirectory_name);
677 }
678 }
679 HeartEventHandler::EndEvent(HeartEventHandler::DATA_CONVERSION);
681
682template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
684{
685 if (!extending)
686 {
687 if (mNodesToOutput.empty())
688 {
689 //Set writer to output all nodes
690 mpWriter->DefineFixedDimension(mpMesh->GetNumNodes());
691 }
692 else
693 {
694 // Added for #2980
695 if (mpMesh->rGetNodePermutation().size() > 0)
696 {
697 if (HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering())
698 {
699 EXCEPTION("HeartConfig setting `GetOutputUsingOriginalNodeOrdering` is meaningless when outputting particular nodes in parallel. (Nodes are written with their original indices by default).");
700 }
701 std::vector<unsigned> nodes_to_output_permuted(mNodesToOutput.size());
702 for (unsigned i = 0; i < mNodesToOutput.size(); i++)
703 {
704 nodes_to_output_permuted[i] = mpMesh->rGetNodePermutation()[mNodesToOutput[i]];
705 }
706 mpWriter->DefineFixedDimension(mNodesToOutput, nodes_to_output_permuted, mpMesh->GetNumNodes());
707 } else {
708 // Output only the nodes indicated
709 mpWriter->DefineFixedDimension(mNodesToOutput, mNodesToOutput, mpMesh->GetNumNodes());
710 }
711 }
712 // mNodeColumnId = mpWriter->DefineVariable("Node", "dimensionless");
713 mVoltageColumnId = mpWriter->DefineVariable("V", "mV");
714
715 // Only used to get an estimate of the # of timesteps below
716 TimeStepper stepper(mCurrentTime,
717 HeartConfig::Instance()->GetSimulationDuration(),
718 HeartConfig::Instance()->GetPrintingTimeStep());
719
720 mpWriter->DefineUnlimitedDimension("Time", "msecs", stepper.EstimateTimeSteps() + 1); // plus one for start and end points
721 }
722 else
723 {
724 mVoltageColumnId = mpWriter->GetVariableByName("V");
725 }
726}
727
728template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
730{
731 mExtraVariablesId.clear();
732 // Check if any extra output variables have been requested
733 if (HeartConfig::Instance()->GetOutputVariablesProvided())
734 {
735 // Get their names in a vector
736 std::vector<std::string> output_variables;
737 HeartConfig::Instance()->GetOutputVariables(output_variables);
738 const unsigned num_vars = output_variables.size();
739 mExtraVariablesId.reserve(num_vars);
740
741 // Loop over them
742 for (unsigned var_index = 0; var_index < num_vars; var_index++)
743 {
744 // Get variable name
745 std::string var_name = output_variables[var_index];
746
747 // Register it (or look it up) in the data writer
748 unsigned column_id;
749 if (extending)
750 {
751 column_id = this->mpWriter->GetVariableByName(var_name);
752 }
753 else
754 {
755 // Difficult to specify the units, as different cell models
756 // at different points in the mesh could be using different units.
757 column_id = this->mpWriter->DefineVariable(var_name, "unknown_units");
758 }
759
760 // Store column id
761 mExtraVariablesId.push_back(column_id);
762 }
763 }
764}
765
766template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
768{
769 // Get the variable names in a vector
770 std::vector<std::string> output_variables;
771 unsigned num_vars = mExtraVariablesId.size();
772 if (num_vars > 0)
773 {
774 HeartConfig::Instance()->GetOutputVariables(output_variables);
775 }
776 assert(output_variables.size() == num_vars);
777
778 // Loop over the requested variables
779 for (unsigned var_index = 0; var_index < num_vars; var_index++)
780 {
781 // Create vector for storing values over the local nodes
782 Vec variable_data = this->mpMesh->GetDistributedVectorFactory()->CreateVec();
783 DistributedVector distributed_var_data = this->mpMesh->GetDistributedVectorFactory()->CreateDistributedVector(variable_data);
784
785 // Loop over the local nodes and gather the data
786 for (DistributedVector::Iterator index = distributed_var_data.Begin();
787 index != distributed_var_data.End();
788 ++index)
789 {
790 // If the region is in the bath
791 if (HeartRegionCode::IsRegionBath(this->mpMesh->GetNode(index.Global)->GetRegion()))
793 // Then we just pad the output with zeros, user currently needs to find a nice
794 // way to deal with this in processing and visualization.
795 distributed_var_data[index] = 0.0;
796 }
797 else
798 {
799 // Find the variable in the cell model and store its value
800 distributed_var_data[index] = this->mpCardiacTissue->GetCardiacCell(index.Global)->GetAnyVariable(output_variables[var_index], mCurrentTime);
801 }
802 }
803 distributed_var_data.Restore();
804
805 // Write it to disc
806 this->mpWriter->PutVector(mExtraVariablesId[var_index], variable_data);
807
808 PetscTools::Destroy(variable_data);
809 }
810}
811
812template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
814{
815 bool extend_file = (mSolution != NULL);
816
817 // I think this is impossible to trip; certainly it's very difficult!
818 assert(!mpWriter);
819
820 if (extend_file)
821 {
823 + "/" + HeartConfig::Instance()->GetOutputFilenamePrefix() + ".h5",
825 //We are going to test for existence before creating the file.
826 //Therefore we should make sure that this existence test is thread-safe.
827 //(If another process creates the file too early then we may get the wrong answer to the
828 //existence question).
829 PetscTools::Barrier("InitialiseWriter::Extension check");
830 if (!h5_file.Exists())
831 {
832 extend_file = false;
833 }
834 else // if it does exist check that it is sensible to extend it by running from the archive we loaded.
835 {
836 Hdf5DataReader reader(HeartConfig::Instance()->GetOutputDirectory(),
837 HeartConfig::Instance()->GetOutputFilenamePrefix(),
838 true);
839 std::vector<double> times = reader.GetUnlimitedDimensionValues();
840 if (times.back() > mCurrentTime)
841 {
842 EXCEPTION("Attempting to extend " << h5_file.GetAbsolutePath() << " with results from time = " << mCurrentTime << ", but it already contains results up to time = " << times.back() << "."
843 " Calling HeartConfig::Instance()->SetOutputDirectory() before Solve() will direct results elsewhere.");
844 }
845 }
846 PetscTools::Barrier("InitialiseWriter::Extension check");
847 }
848 mpWriter = new Hdf5DataWriter(*mpMesh->GetDistributedVectorFactory(),
851 !extend_file, // don't clear directory if extension requested
852 extend_file,
853 "Data",
854 mUseHdf5DataWriterCache);
855
856 /* If user has specified a chunk size and alignment parameter, pass it
857 * through. We set them to the same value as we think this is the most
858 * likely use case, specifically on striped filesystems where a chunk
859 * should squeeze into a stripe.
860 * Only happens if !extend_file, i.e. we're NOT loading a checkpoint, or
861 * we are loading a checkpoint but the H5 file doesn't exist yet.
862 */
863 if (!extend_file && mHdf5DataWriterChunkSizeAndAlignment)
864 {
865 mpWriter->SetTargetChunkSize(mHdf5DataWriterChunkSizeAndAlignment);
866 mpWriter->SetAlignment(mHdf5DataWriterChunkSizeAndAlignment);
867 }
868
869 // Define columns, or get the variable IDs from the writer
870 DefineWriterColumns(extend_file);
871
872 // Possibility of applying a permutation
873 if (HeartConfig::Instance()->GetOutputUsingOriginalNodeOrdering())
874 {
875 bool success = mpWriter->ApplyPermutation(mpMesh->rGetNodePermutation(), true /*unsafe mode - extending*/);
876 if (success == false)
877 {
878 //It's not really a permutation, so reset
880 }
881 }
882
883 if (!extend_file)
884 {
885 mpWriter->EndDefineMode();
886 }
887
888 return extend_file;
889}
890
891template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
893{
894 mUseHdf5DataWriterCache = useCache;
895}
896
897template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
902
903template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
905{
906 mNodesToOutput = nodesToOutput;
907}
908
909template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
911{
912 if ((HeartConfig::Instance()->GetOutputDirectory() == "") || (HeartConfig::Instance()->GetOutputFilenamePrefix() == ""))
913 {
914 EXCEPTION("Data reader invalid as data writer cannot be initialised");
915 }
916 return Hdf5DataReader(HeartConfig::Instance()->GetOutputDirectory(), HeartConfig::Instance()->GetOutputFilenamePrefix());
917}
918
919template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
924
925template <unsigned ELEMENT_DIM, unsigned SPACE_DIM, unsigned PROBLEM_DIM>
929
930// Explicit instantiation
931
932// Monodomain
938
939// Bidomain
943
944// Extended Bidomain
#define EXCEPTION(message)
#define NEVER_REACHED
void SetWriteInfo(bool writeInfo=true)
void SetOutputNodes(std::vector< unsigned > &rNodesToOutput)
void DefineExtraVariablesWriterColumns(bool extending)
DistributedVector GetSolutionDistributedVector()
void SetHdf5DataWriterTargetChunkSizeAndAlignment(hsize_t size)
void SetUseTimeAdaptivityController(bool useAdaptivity, AbstractTimeAdaptivityController *pController=NULL)
void SetMesh(AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > *pMesh)
AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > & rGetMesh()
void PrintOutput(bool rPrintOutput)
void SetBoundaryConditionsContainer(BccType pBcc)
AbstractCardiacCellFactory< ELEMENT_DIM, SPACE_DIM > * mpCellFactory
std::vector< unsigned > mNodesToOutput
AbstractCardiacTissue< ELEMENT_DIM, SPACE_DIM > * GetTissue()
void SetUseHdf5DataWriterCache(bool useCache=true)
virtual void DefineWriterColumns(bool extending)
DistributedVector CreateDistributedVector(Vec vec, bool readOnly=false)
std::string GetAbsolutePath() const
bool Exists() const
std::vector< double > GetUnlimitedDimensionValues()
void GetSheetDimensions(c_vector< double, 2 > &sheetDimensions) const
double GetPdeTimeStep() const
void GetOutputVariables(std::vector< std::string > &rOutputVariables) const
double GetSimulationDuration() const
void SetOutputUsingOriginalNodeOrdering(bool useOriginal)
std::string GetOutputFilenamePrefix() const
void GetSlabDimensions(c_vector< double, 3 > &slabDimensions) const
void Write(bool useArchiveLocationInfo=false, std::string subfolderName="output")
double GetInterNodeSpace() const
std::string GetOutputDirectory() const
void GetFibreLength(c_vector< double, 1 > &fibreLength) const
static HeartConfig * Instance()
static bool IsRegionBath(HeartRegionType regionId)
static std::string GetChasteTestOutputDirectory()
static void Destroy(Vec &rVec)
static void Barrier(const std::string callerId="")
static bool IsParallel()
static void ReplicateException(bool flag)
bool IsTimeAtEnd() const
double GetTime() const
void AdvanceOneTimeStep()
double GetNextTime() const
unsigned EstimateTimeSteps() const