Chaste Commit::1fd4e48e3990e67db148bc1bc4cf6991a0049d0c
AbstractConvergenceTester.hpp
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
37#ifndef ABSTRACTCONVERGENCETESTER_HPP_
38#define ABSTRACTCONVERGENCETESTER_HPP_
39
40#include "BidomainProblem.hpp"
41#include "MonodomainProblem.hpp"
42
43#include <petscvec.h>
44#include <vector>
45#include <iostream>
46#include <fstream>
47#include <cmath>
48#include <ctime>
49
50#include "AbstractTetrahedralMesh.hpp"
51#include "TrianglesMeshReader.hpp"
52#include "AbstractCardiacCellFactory.hpp"
53#include "OutputFileHandler.hpp"
54#include "TrianglesMeshWriter.hpp"
55#include "PropagationPropertiesCalculator.hpp"
56#include "Hdf5DataReader.hpp"
57#include "GeneralPlaneStimulusCellFactory.hpp"
58#include "CuboidMeshConstructor.hpp"
59#include "ZeroStimulusCellFactory.hpp"
60#include "SimpleStimulus.hpp"
61#include "ConstBoundaryCondition.hpp"
62#include "StimulusBoundaryCondition.hpp"
63
64#include "Warnings.hpp"
65#include "Timer.hpp"
66
67typedef enum StimulusType_
68{
69 PLANE=0,
70 QUARTER,
71 NEUMANN
72} StimulusType;
73
78template <class CELL, unsigned DIM>
80{
81private:
83 std::vector< boost::shared_ptr<SimpleStimulus> > mpStimuli;
85 double mMeshWidth;
87 double mStepSize;
91 unsigned mLevels;
92public:
93
100 RampedQuarterStimulusCellFactory(double meshWidth, unsigned numElemAcross, double fullStim=-1e7)
102 mMeshWidth(meshWidth),
103 mStepSize(meshWidth/numElemAcross),
104 mLevels(numElemAcross/4)
105 {
106 assert(numElemAcross%4 == 0); //numElemAcross is supposed to be a multiple of 4
107
108 for (unsigned level=0; level<mLevels; level++)
109 {
110 double this_stim = fullStim - (level*fullStim)/mLevels;
111 //this_stim is full_stim at the zero level and would be zero at level=mLevels
112 mpStimuli.push_back((boost::shared_ptr<SimpleStimulus>)new SimpleStimulus(this_stim, 0.5));
113 }
114 }
115
116
123 {
124 double x = pNode->GetPoint()[0];
125 double d_level = x/mStepSize;
126 unsigned level = (unsigned) d_level;
127 assert(fabs(level-d_level) < DBL_MAX); //x ought to really be a multiple of the step size
128
129 if (level < mLevels)
130 {
131 return new CELL(this->mpSolver, this->mpStimuli[level]);
132 }
133 else
134 {
135 return new CELL(this->mpSolver, this->mpZeroStimulus);
136 }
137 }
138};
139
140
195
199template<class CARDIAC_PROBLEM, unsigned DIM>
200void SetConductivities(CARDIAC_PROBLEM& rCardiacProblem);
201
202template<unsigned DIM>
203void SetConductivities(BidomainProblem<DIM>& rProblem)
204{
205 c_vector<double, DIM> conductivities;
206 for (unsigned i=0; i<DIM; i++)
207 {
208 conductivities[i] = 1.75;
209 }
211
212 for (unsigned i=0; i<DIM; i++)
213 {
214 conductivities[i] = 7.0;
215 }
217}
218
219template<unsigned DIM>
220void SetConductivities(MonodomainProblem<DIM>& rProblem)
221{
222 c_vector<double, DIM> conductivities;
223 for (unsigned i=0; i<DIM; i++)
224 {
225 conductivities[i] = 1.75;
226 }
228}
229
234template<class CELL, class CARDIAC_PROBLEM, unsigned DIM, unsigned PROBLEM_DIM>
236{
237public:
243 void Converge(std::string nameOfTest)
244 {
245 // Create the meshes on which the test will be based
246 const std::string mesh_dir = "ConvergenceMesh";
247 OutputFileHandler output_file_handler(mesh_dir);
248 ReplicatableVector voltage_replicated;
249
250 unsigned file_num=0;
251
252 // Create a file for storing conduction velocity and AP data and write the header
253 OutputFileHandler conv_info_handler("ConvergencePlots"+nameOfTest, false);
254 out_stream p_conv_info_file;
256 {
257 std::cout << "=========================== Beginning Test...==================================\n";
258 p_conv_info_file = conv_info_handler.OpenOutputFile(nameOfTest+"_info.csv");
259 (*p_conv_info_file) << "#Abcisa\t"
260 << "l2-norm-full\t"
261 << "l2-norm-onset\t"
262 << "Max absolute err\t"
263 << "APD90_1st_quad\t"
264 << "APD90_3rd_quad\t"
265 << "Conduction velocity (relative diffs)" << std::endl;
266 }
268
269 double prev_apd90_first_qn=0.0;
270 double prev_apd90_third_qn=0.0;
271 double prev_cond_velocity=0.0;
272 std::vector<double> prev_voltage;
273 std::vector<double> prev_times;
274 PopulateStandardResult(prev_voltage, prev_times);
275
276 do
277 {
278 CuboidMeshConstructor<DIM> constructor;
279
280 //If the printing time step is too fine, then simulations become I/O bound without much improvement in accuracy
281 double printing_step = this->PdeTimeStep;
282// LCOV_EXCL_START
283 while (printing_step < 1.0e-4)
284 {
285 printing_step *= 2.0;
286 std::cout<<"Warning: PrintingTimeStep increased\n";
287 }
288// LCOV_EXCL_STOP
290// LCOV_EXCL_START
292 {
294 }
295 else
296 {
298 }
299// LCOV_EXCL_STOP
300 HeartConfig::Instance()->SetOutputDirectory("Convergence"+nameOfTest);
302
304 constructor.Construct(mesh, this->MeshNum, mMeshWidth);
305
306 unsigned num_ele_across = SmallPow(2u, this->MeshNum+2); // number of elements in each dimension
307
308 AbstractCardiacCellFactory<DIM>* p_cell_factory=NULL;
309
310 switch (this->Stimulus)
311 {
312 case NEUMANN:
313 {
314 p_cell_factory = new ZeroStimulusCellFactory<CELL, DIM>();
315 break;
316 }
317 case PLANE:
318 {
319 p_cell_factory = new GeneralPlaneStimulusCellFactory<CELL, DIM>(num_ele_across, constructor.GetWidth(), this->AbsoluteStimulus);
320 break;
321 }
322 case QUARTER:
323 {
325 p_cell_factory = new RampedQuarterStimulusCellFactory<CELL, DIM>(constructor.GetWidth(), num_ele_across, this->AbsoluteStimulus/10.0);
326 break;
327 }
328 default:
330 }
331
332
333 CARDIAC_PROBLEM cardiac_problem(p_cell_factory);
334 cardiac_problem.SetMesh(&mesh);
335
336 // Calculate positions of nodes 1/4 and 3/4 through the mesh
337 unsigned third_quadrant_node;
338 unsigned first_quadrant_node;
339 switch(DIM)
340 {
341 case 1:
342 {
343 first_quadrant_node = (unsigned) (0.25*constructor.GetNumElements());
344 third_quadrant_node = (unsigned) (0.75*constructor.GetNumElements());
345 break;
346 }
347 case 2:
348 {
349 unsigned n= SmallPow (2u, this->MeshNum+2);
350 first_quadrant_node = (n+1)*(n/2)+ n/4 ;
351 third_quadrant_node = (n+1)*(n/2)+3*n/4 ;
352 break;
353 }
354 case 3:
355 {
356 const unsigned first_quadrant_nodes_3d[5]={61, 362, 2452, 17960, 137296};
357 const unsigned third_quadrant_nodes_3d[5]={63, 366, 2460, 17976, 137328};
358 assert(this->PdeTimeStep<5);
359 first_quadrant_node = first_quadrant_nodes_3d[this->MeshNum];
360 third_quadrant_node = third_quadrant_nodes_3d[this->MeshNum];
361 break;
362 }
363
364 default:
366 }
367
368 double mesh_width=constructor.GetWidth();
369
370 // We only need the output of these two nodes
371 std::vector<unsigned> nodes_to_be_output;
372 nodes_to_be_output.push_back(first_quadrant_node);
373 nodes_to_be_output.push_back(third_quadrant_node);
374 cardiac_problem.SetOutputNodes(nodes_to_be_output);
375
376 // The results of the tests were originally obtained with the following conductivity
377 // values. After implementing fibre orientation the defaults changed. Here we set
378 // the former ones to be used.
379 SetConductivities(cardiac_problem);
380
381 cardiac_problem.Initialise();
382
383 boost::shared_ptr<BoundaryConditionsContainer<DIM,DIM,PROBLEM_DIM> > p_bcc(new BoundaryConditionsContainer<DIM,DIM,PROBLEM_DIM>);
385 if (Stimulus==NEUMANN)
386 {
387
389
390 // get mesh
391 AbstractTetrahedralMesh<DIM, DIM> &r_mesh = cardiac_problem.rGetMesh();
392 // loop over boundary elements
394 iter = r_mesh.GetBoundaryElementIteratorBegin();
395 while (iter != r_mesh.GetBoundaryElementIteratorEnd())
396 {
397 double x = ((*iter)->CalculateCentroid())[0];
399 if (x*x<=1e-10)
400 {
401 p_bcc->AddNeumannBoundaryCondition(*iter, p_bc_stim);
402 }
403 iter++;
404 }
405 // pass the bcc to the problem
406 cardiac_problem.SetBoundaryConditionsContainer(p_bcc);
407 }
408
409 DisplayRun();
410 Timer::Reset();
412 //cardiac_problem.SetWriteInfo();
413
414 try
415 {
416 cardiac_problem.Solve();
417 }
418 catch (const Exception& e)
419 {
420 WARNING("This run threw an exception. Check convergence results\n");
421 std::cout << e.GetMessage() << std::endl;
422 }
423
425 {
426 std::cout << "Time to solve = " << Timer::GetElapsedTime() << " seconds\n";
427 }
428
429 OutputFileHandler results_handler("Convergence"+nameOfTest, false);
430 Hdf5DataReader results_reader = cardiac_problem.GetDataReader();
431
432 {
433 std::vector<double> transmembrane_potential = results_reader.GetVariableOverTime("V", third_quadrant_node);
434 std::vector<double> time_series = results_reader.GetUnlimitedDimensionValues();
435
436 OutputFileHandler plot_file_handler("ConvergencePlots"+nameOfTest, false);
438 {
439 // Write out the time series for the node at third quadrant
440 {
441 std::stringstream plot_file_name_stream;
442 plot_file_name_stream<< nameOfTest << "_Third_quadrant_node_run_"<< file_num << ".csv";
443 out_stream p_plot_file = plot_file_handler.OpenOutputFile(plot_file_name_stream.str());
444 for (unsigned data_point = 0; data_point<time_series.size(); data_point++)
445 {
446 (*p_plot_file) << time_series[data_point] << "\t" << transmembrane_potential[data_point] << "\n";
447 }
448 p_plot_file->close();
449 }
450 // Write time series for first quadrant node
451 {
452 std::vector<double> transmembrane_potential_1qd=results_reader.GetVariableOverTime("V", first_quadrant_node);
453 std::vector<double> time_series_1qd = results_reader.GetUnlimitedDimensionValues();
454 std::stringstream plot_file_name_stream;
455 plot_file_name_stream<< nameOfTest << "_First_quadrant_node_run_"<< file_num << ".csv";
456 out_stream p_plot_file = plot_file_handler.OpenOutputFile(plot_file_name_stream.str());
457 for (unsigned data_point = 0; data_point<time_series.size(); data_point++)
458 {
459 (*p_plot_file) << time_series_1qd[data_point] << "\t" << transmembrane_potential_1qd[data_point] << "\n";
460 }
461 p_plot_file->close();
462 }
463 }
464 // calculate conduction velocity and APD90 error
465 PropagationPropertiesCalculator ppc(&results_reader);
466
467 try
468 {
469 // LCOV_EXCL_START
471 {
472 Apd90FirstQn = ppc.CalculateActionPotentialDuration(90.0, first_quadrant_node);
473 Apd90ThirdQn = ppc.CalculateActionPotentialDuration(90.0, third_quadrant_node);
474 }
475 // LCOV_EXCL_STOP
476 ConductionVelocity = ppc.CalculateConductionVelocity(first_quadrant_node,third_quadrant_node,0.5*mesh_width);
477 }
478 // LCOV_EXCL_START
479 catch (const Exception& e)
480 {
481 std::cout << "Warning - this run threw an exception in calculating propagation. Check convergence results\n";
482 std::cout << e.GetMessage() << std::endl;
483 }
484 // LCOV_EXCL_STOP
485 double cond_velocity_error = 1e10;
486 double apd90_first_qn_error = 1e10;
487 double apd90_third_qn_error = 1e10;
488
489 if (this->PopulatedResult)
490 {
491 if (prev_cond_velocity != 0.0)
492 {
493 cond_velocity_error = fabs(ConductionVelocity - prev_cond_velocity) / prev_cond_velocity;
494 }
495// LCOV_EXCL_START
496 if (prev_apd90_first_qn != 0.0)
497 {
498 apd90_first_qn_error = fabs(Apd90FirstQn - prev_apd90_first_qn) / prev_apd90_first_qn;
499 }
500 if (prev_apd90_third_qn != 0.0)
501 {
502 apd90_third_qn_error = fabs(Apd90ThirdQn - prev_apd90_third_qn) / prev_apd90_third_qn;
503 }
504 if (apd90_first_qn_error == 0.0)
505 {
506 apd90_first_qn_error = DBL_EPSILON; //Avoid log zero on plot
507 }
508 if (apd90_third_qn_error == 0.0)
509 {
510 apd90_third_qn_error = DBL_EPSILON; //Avoid log zero on plot
511 }
512// LCOV_EXCL_STOP
513 if (cond_velocity_error == 0.0)
514 {
515 cond_velocity_error = DBL_EPSILON; //Avoid log zero on plot
516 }
517 }
518
519 prev_cond_velocity = ConductionVelocity;
520 prev_apd90_first_qn = Apd90FirstQn;
521 prev_apd90_third_qn = Apd90ThirdQn;
522
523 // calculate l2norm
524 double max_abs_error = 0;
525 double sum_sq_abs_error =0;
526 double sum_sq_prev_voltage = 0;
527 double sum_sq_abs_error_full =0;
528 double sum_sq_prev_voltage_full = 0;
529 if (this->PopulatedResult)
530 {
531 //If the PDE step is varying then we'll have twice as much data now as we use to have
532
533 unsigned time_factor=(time_series.size()-1) / (prev_times.size()-1);
534 assert (time_factor == 1 || time_factor == 2 || time_factor == 8);
535 //Iterate over the shorter time series data
536 for (unsigned data_point = 0; data_point<prev_times.size(); data_point++)
537 {
538 unsigned this_data_point=time_factor*data_point;
539
540 assert(time_series[this_data_point] == prev_times[data_point]);
541 double abs_error = fabs(transmembrane_potential[this_data_point]-prev_voltage[data_point]);
542 max_abs_error = (abs_error > max_abs_error) ? abs_error : max_abs_error;
543 //Only do resolve the upstroke...
544 sum_sq_abs_error_full += abs_error*abs_error;
545 sum_sq_prev_voltage_full += prev_voltage[data_point] * prev_voltage[data_point];
546 if (time_series[this_data_point] <= 8.0)
547 {
548 //In most regular cases we always do this, since the simulation stops at ms
549 sum_sq_abs_error += abs_error*abs_error;
550 sum_sq_prev_voltage += prev_voltage[data_point] * prev_voltage[data_point];
551 }
552 }
553
554 }
555 if (!this->PopulatedResult || !FixedResult)
556 {
557 prev_voltage = transmembrane_potential;
558 prev_times = time_series;
559 }
560
561 if (this->PopulatedResult)
562 {
563 double l2_norm_upstroke = sqrt(sum_sq_abs_error/sum_sq_prev_voltage);
564 double l2_norm_full = sqrt(sum_sq_abs_error_full/sum_sq_prev_voltage_full);
565
567 {
568 (*p_conv_info_file) << std::setprecision(8)
569 << Abscissa() << "\t"
570 << l2_norm_full << "\t"
571 << l2_norm_upstroke << "\t"
572 << max_abs_error << "\t"
573 << Apd90FirstQn <<" "<< apd90_first_qn_error <<""<< "\t"
574 << Apd90ThirdQn <<" "<< apd90_third_qn_error <<""<< "\t"
575 << ConductionVelocity <<" "<< cond_velocity_error <<""<< std::endl;
576 }
577 // convergence criterion
578 this->Converged = l2_norm_full < this->RelativeConvergenceCriterion;
579 this->LastDifference = l2_norm_full;
580// LCOV_EXCL_START
581 assert (time_series.size() != 1u);
582 if (time_series.back() == 0.0)
583 {
584 std::cout << "Failed after successful convergence - give up this convergence test\n";
585 break;
586 }
587// LCOV_EXCL_STOP
588
589 }
590
591 if (time_series.back() != 0.0)
592 {
593 // Simulation ran to completion
594 this->PopulatedResult=true;
595
596 }
597 }
598
599 // Get ready for the next test by halving the time step
600 if (!this->Converged)
601 {
603 file_num++;
604 }
605 delete p_cell_factory;
606 }
607 while (!GiveUpConvergence() && !this->Converged);
608
609
611 {
612 p_conv_info_file->close();
613
614 std::cout << "Results: " << std::endl;
615 FileFinder info_finder = conv_info_handler.FindFile(nameOfTest + "_info.csv");
616 std::ifstream info_file(info_finder.GetAbsolutePath().c_str());
617 if (info_file)
618 {
619 std::cout << info_file.rdbuf();
620 info_file.close();
621 }
622 }
623 }
624
629 {
631 {
632 return; //Only master displays this
633 }
634 unsigned num_ele_across = SmallPow(2u, this->MeshNum+2);// number of elements in each dimension
635 double scaling = mMeshWidth/(double) num_ele_across;
636
637 std::cout<<"================================================================================"<<std::endl;
638 std::cout<<"Solving in " << DIM << "D\t";
639 std::cout<<"Space step " << scaling << " cm (mesh " << this->MeshNum << ")" << "\n";
640 std::cout<<"PDE step " << this->PdeTimeStep << " ms" << "\t";
641 std::cout<<"ODE step " << this->OdeTimeStep << " ms" << "\t";
643 {
644 std::cout<<"KSP absolute "<<HeartConfig::Instance()->GetAbsoluteTolerance()<<"\t";
645 }
646 else
647 {
648 std::cout<<"KSP relative "<<HeartConfig::Instance()->GetRelativeTolerance()<<"\t";
649 }
650 switch (this->Stimulus)
651 {
652 case PLANE:
653 std::cout<<"Stimulus = Plane\n";
654 break;
655
656 case QUARTER:
657 std::cout<<"Stimulus = Ramped first quarter\n";
658 break;
659
660 case NEUMANN:
661 std::cout<<"Stimulus = Neumann\n";
662 break;
663
664 }
665 // Keep track of what Nightly things are doing
666 std::time_t rawtime;
667 std::time( &rawtime );
668 std::cout << std::ctime(&rawtime);
669 std::cout << std::flush;
670 //HeartEventHandler::Headings();
671 //HeartEventHandler::Report();
672
673 }
674
675public:
676 virtual ~AbstractConvergenceTester() {}
677
683 virtual bool GiveUpConvergence()=0;
685 virtual double Abscissa()=0;
692 virtual void PopulateStandardResult(std::vector<double> &result, std::vector<double> &times)
693 {
694 assert(this->PopulatedResult==false);
695 assert(result.size()==0);
696 assert(times.size()==0);
697 }
698
703 {
704 return Converged;
705 }
706
710 void SetMeshWidth(double meshWidth)
711 {
712 mMeshWidth=meshWidth;
713 }
714};
715
716#endif /*ABSTRACTCONVERGENCETESTER_HPP_*/
#define NEVER_REACHED
boost::shared_ptr< AbstractIvpOdeSolver > mpSolver
boost::shared_ptr< ZeroStimulus > mpZeroStimulus
void Converge(std::string nameOfTest)
virtual void UpdateConvergenceParameters()=0
virtual double Abscissa()=0
virtual void SetInitialConvergenceParameters()=0
virtual bool GiveUpConvergence()=0
virtual void PopulateStandardResult(std::vector< double > &result, std::vector< double > &times)
std::vector< BoundaryElement< ELEMENT_DIM-1, SPACE_DIM > * >::const_iterator BoundaryElementIterator
BoundaryElementIterator GetBoundaryElementIteratorBegin() const
BoundaryElementIterator GetBoundaryElementIteratorEnd() const
virtual void Converge(std::string nameOfTest)=0
void Construct(AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, unsigned meshRefinementNum, double meshWidth)
std::string GetAbsolutePath() const
std::vector< double > GetUnlimitedDimensionValues()
std::vector< double > GetVariableOverTime(const std::string &rVariableName, unsigned nodeIndex)
void SetIntracellularConductivities(const c_vector< double, 3 > &rIntraConductivities)
double GetAbsoluteTolerance() const
void SetOutputDirectory(const std::string &rOutputDirectory)
bool GetUseAbsoluteTolerance() const
double GetRelativeTolerance() const
void SetExtracellularConductivities(const c_vector< double, 3 > &rExtraConductivities)
void SetSimulationDuration(double simulationDuration)
void SetOutputFilenamePrefix(const std::string &rOutputFilenamePrefix)
static HeartConfig * Instance()
void SetOdePdeAndPrintingTimeSteps(double odeTimeStep, double pdeTimeStep, double printingTimeStep)
Definition Node.hpp:59
ChastePoint< SPACE_DIM > GetPoint() const
Definition Node.cpp:133
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 AmMaster()
double CalculateConductionVelocity(unsigned globalNearNodeIndex, unsigned globalFarNodeIndex, const double euclideanDistance)
double CalculateActionPotentialDuration(const double percentage, unsigned globalNodeIndex)
AbstractCardiacCellInterface * CreateCardiacCellForTissueNode(Node< DIM > *pNode)
RampedQuarterStimulusCellFactory(double meshWidth, unsigned numElemAcross, double fullStim=-1e7)
std::vector< boost::shared_ptr< SimpleStimulus > > mpStimuli
static double GetElapsedTime()
Definition Timer.cpp:54
static void Reset()
Definition Timer.cpp:44