// Solve on a 1D string of cells, 1mm long with a space step of 0.1mm. void TestMonodomainConstantStimulus() { // this parameters are a bit arbitrary, and chosen to get a good spread of voltages HeartConfig::Instance()->SetIntracellularConductivities(Create_c_vector(1.75)); HeartConfig::Instance()->SetSimulationDuration(2); //ms HeartConfig::Instance()->SetMeshFileName("mesh/test/data/1D_0_to_1mm_10_elements"); HeartConfig::Instance()->SetOutputDirectory("MonoNeumannConst"); HeartConfig::Instance()->SetOutputFilenamePrefix("MonodomainLR91_1d"); ZeroStimulusCellFactory<CellLuoRudy1991FromCellML, 1> cell_factory; MonodomainProblem<1> monodomain_problem( &cell_factory ); monodomain_problem.Initialise(); HeartConfig::Instance()->SetSurfaceAreaToVolumeRatio(1*1.75/0.0005); // create boundary conditions container boost::shared_ptr<BoundaryConditionsContainer<1,1,1> > p_bcc(new BoundaryConditionsContainer<1,1,1>); ConstBoundaryCondition<1>* p_bc_stim = new ConstBoundaryCondition<1>(2*1.75/0.0005); // get mesh AbstractTetrahedralMesh<1,1> &mesh = monodomain_problem.rGetMesh(); // loop over boundary elements AbstractTetrahedralMesh<1, 1>::BoundaryElementIterator iter; iter = mesh.GetBoundaryElementIteratorBegin(); while (iter != mesh.GetBoundaryElementIteratorEnd()) { // if the element is on the left of the mesh, add a stimulus to the bcc if (((*iter)->GetNodeLocation(0))[0]==0.0) { p_bcc->AddNeumannBoundaryCondition(*iter, p_bc_stim); } iter++; } // pass the bcc to the monodomain problem monodomain_problem.SetBoundaryConditionsContainer(p_bcc); monodomain_problem.Solve(); // check some voltages ReplicatableVector voltage_replicated(monodomain_problem.GetSolution()); double atol=5e-3; TS_ASSERT_DELTA(voltage_replicated[1], 94.6426, atol); TS_ASSERT_DELTA(voltage_replicated[3], 49.7867, atol); TS_ASSERT_DELTA(voltage_replicated[5], 30.5954, atol); TS_ASSERT_DELTA(voltage_replicated[7], 21.6782, atol); TS_ASSERT_DELTA(voltage_replicated[9], -33.9983, atol); TS_ASSERT_DELTA(voltage_replicated[10], -52.2396, atol); }
/** * Run the same test at different levels of refinement until some convergence criterion is met. * @param nameOfTest The name of the convergence test (typically the name in the suite) for use in naming files. * \todo This is a scarily long method; could do with some parts extracted? */ void Converge(std::string nameOfTest) { // Create the meshes on which the test will be based const std::string mesh_dir = "ConvergenceMesh"; OutputFileHandler output_file_handler(mesh_dir); ReplicatableVector voltage_replicated; unsigned file_num=0; // Create a file for storing conduction velocity and AP data and write the header OutputFileHandler conv_info_handler("ConvergencePlots"+nameOfTest, false); out_stream p_conv_info_file; if (PetscTools::AmMaster()) { std::cout << "=========================== Beginning Test...==================================\n"; p_conv_info_file = conv_info_handler.OpenOutputFile(nameOfTest+"_info.csv"); (*p_conv_info_file) << "#Abcisa\t" << "l2-norm-full\t" << "l2-norm-onset\t" << "Max absolute err\t" << "APD90_1st_quad\t" << "APD90_3rd_quad\t" << "Conduction velocity (relative diffs)" << std::endl; } SetInitialConvergenceParameters(); double prev_apd90_first_qn=0.0; double prev_apd90_third_qn=0.0; double prev_cond_velocity=0.0; std::vector<double> prev_voltage; std::vector<double> prev_times; PopulateStandardResult(prev_voltage, prev_times); do { CuboidMeshConstructor<DIM> constructor; //If the printing time step is too fine, then simulations become I/O bound without much improvement in accuracy double printing_step = this->PdeTimeStep; #define COVERAGE_IGNORE while (printing_step < 1.0e-4) { printing_step *= 2.0; std::cout<<"Warning: PrintingTimeStep increased\n"; } #undef COVERAGE_IGNORE HeartConfig::Instance()->SetOdePdeAndPrintingTimeSteps(this->OdeTimeStep, this->PdeTimeStep, printing_step); #define COVERAGE_IGNORE if (SimulateFullActionPotential) { HeartConfig::Instance()->SetSimulationDuration(400.0); } else { HeartConfig::Instance()->SetSimulationDuration(8.0); } #undef COVERAGE_IGNORE HeartConfig::Instance()->SetOutputDirectory("Convergence"+nameOfTest); HeartConfig::Instance()->SetOutputFilenamePrefix("Results"); DistributedTetrahedralMesh<DIM, DIM> mesh; constructor.Construct(mesh, this->MeshNum, mMeshWidth); unsigned num_ele_across = SmallPow(2u, this->MeshNum+2); // number of elements in each dimension AbstractCardiacCellFactory<DIM>* p_cell_factory=NULL; switch (this->Stimulus) { case NEUMANN: { p_cell_factory = new ZeroStimulusCellFactory<CELL, DIM>(); break; } case PLANE: { if (this->UseAbsoluteStimulus) { #define COVERAGE_IGNORE p_cell_factory = new GeneralPlaneStimulusCellFactory<CELL, DIM>(0, this->AbsoluteStimulus, true); #undef COVERAGE_IGNORE } else { p_cell_factory = new GeneralPlaneStimulusCellFactory<CELL, DIM>(num_ele_across, constructor.GetWidth(), false, this->AbsoluteStimulus); } break; } case QUARTER: { ///\todo consider reducing all stimuli to match this one. p_cell_factory = new RampedQuarterStimulusCellFactory<CELL, DIM>(constructor.GetWidth(), num_ele_across, this->AbsoluteStimulus/10.0); break; } default: NEVER_REACHED; } CARDIAC_PROBLEM cardiac_problem(p_cell_factory); cardiac_problem.SetMesh(&mesh); // Calculate positions of nodes 1/4 and 3/4 through the mesh unsigned third_quadrant_node; unsigned first_quadrant_node; switch(DIM) { case 1: { first_quadrant_node = (unsigned) (0.25*constructor.GetNumElements()); third_quadrant_node = (unsigned) (0.75*constructor.GetNumElements()); break; } case 2: { unsigned n= SmallPow (2u, this->MeshNum+2); first_quadrant_node = (n+1)*(n/2)+ n/4 ; third_quadrant_node = (n+1)*(n/2)+3*n/4 ; break; } case 3: { const unsigned first_quadrant_nodes_3d[5]={61, 362, 2452, 17960, 137296}; const unsigned third_quadrant_nodes_3d[5]={63, 366, 2460, 17976, 137328}; assert(this->PdeTimeStep<5); first_quadrant_node = first_quadrant_nodes_3d[this->MeshNum]; third_quadrant_node = third_quadrant_nodes_3d[this->MeshNum]; break; } default: NEVER_REACHED; } double mesh_width=constructor.GetWidth(); // We only need the output of these two nodes std::vector<unsigned> nodes_to_be_output; nodes_to_be_output.push_back(first_quadrant_node); nodes_to_be_output.push_back(third_quadrant_node); cardiac_problem.SetOutputNodes(nodes_to_be_output); // The results of the tests were originally obtained with the following conductivity // values. After implementing fibre orientation the defaults changed. Here we set // the former ones to be used. SetConductivities(cardiac_problem); cardiac_problem.Initialise(); boost::shared_ptr<BoundaryConditionsContainer<DIM,DIM,PROBLEM_DIM> > p_bcc(new BoundaryConditionsContainer<DIM,DIM,PROBLEM_DIM>); SimpleStimulus stim(NeumannStimulus, 0.5); if (Stimulus==NEUMANN) { StimulusBoundaryCondition<DIM>* p_bc_stim = new StimulusBoundaryCondition<DIM>(&stim); // get mesh AbstractTetrahedralMesh<DIM, DIM> &r_mesh = cardiac_problem.rGetMesh(); // loop over boundary elements typename AbstractTetrahedralMesh<DIM, DIM>::BoundaryElementIterator iter; iter = r_mesh.GetBoundaryElementIteratorBegin(); while (iter != r_mesh.GetBoundaryElementIteratorEnd()) { double x = ((*iter)->CalculateCentroid())[0]; ///\todo remove magic number? (#1884) if (x*x<=1e-10) { p_bcc->AddNeumannBoundaryCondition(*iter, p_bc_stim); } iter++; } // pass the bcc to the problem cardiac_problem.SetBoundaryConditionsContainer(p_bcc); } DisplayRun(); Timer::Reset(); //// use this to get some info printed out //cardiac_problem.SetWriteInfo(); try { cardiac_problem.Solve(); } catch (Exception e) { WARNING("This run threw an exception. Check convergence results\n"); std::cout << e.GetMessage() << std::endl; } if (PetscTools::AmMaster()) { std::cout << "Time to solve = " << Timer::GetElapsedTime() << " seconds\n"; } OutputFileHandler results_handler("Convergence"+nameOfTest, false); Hdf5DataReader results_reader = cardiac_problem.GetDataReader(); { std::vector<double> transmembrane_potential = results_reader.GetVariableOverTime("V", third_quadrant_node); std::vector<double> time_series = results_reader.GetUnlimitedDimensionValues(); OutputFileHandler plot_file_handler("ConvergencePlots"+nameOfTest, false); if (PetscTools::AmMaster()) { // Write out the time series for the node at third quadrant { std::stringstream plot_file_name_stream; plot_file_name_stream<< nameOfTest << "_Third_quadrant_node_run_"<< file_num << ".csv"; out_stream p_plot_file = plot_file_handler.OpenOutputFile(plot_file_name_stream.str()); for (unsigned data_point = 0; data_point<time_series.size(); data_point++) { (*p_plot_file) << time_series[data_point] << "\t" << transmembrane_potential[data_point] << "\n"; } p_plot_file->close(); } // Write time series for first quadrant node { std::vector<double> transmembrane_potential_1qd=results_reader.GetVariableOverTime("V", first_quadrant_node); std::vector<double> time_series_1qd = results_reader.GetUnlimitedDimensionValues(); std::stringstream plot_file_name_stream; plot_file_name_stream<< nameOfTest << "_First_quadrant_node_run_"<< file_num << ".csv"; out_stream p_plot_file = plot_file_handler.OpenOutputFile(plot_file_name_stream.str()); for (unsigned data_point = 0; data_point<time_series.size(); data_point++) { (*p_plot_file) << time_series_1qd[data_point] << "\t" << transmembrane_potential_1qd[data_point] << "\n"; } p_plot_file->close(); } } // calculate conduction velocity and APD90 error PropagationPropertiesCalculator ppc(&results_reader); try { #define COVERAGE_IGNORE if (SimulateFullActionPotential) { Apd90FirstQn = ppc.CalculateActionPotentialDuration(90.0, first_quadrant_node); Apd90ThirdQn = ppc.CalculateActionPotentialDuration(90.0, third_quadrant_node); } #undef COVERAGE_IGNORE ConductionVelocity = ppc.CalculateConductionVelocity(first_quadrant_node,third_quadrant_node,0.5*mesh_width); } catch (Exception e) { #define COVERAGE_IGNORE std::cout << "Warning - this run threw an exception in calculating propagation. Check convergence results\n"; std::cout << e.GetMessage() << std::endl; #undef COVERAGE_IGNORE } double cond_velocity_error = 1e10; double apd90_first_qn_error = 1e10; double apd90_third_qn_error = 1e10; if (this->PopulatedResult) { if (prev_cond_velocity != 0.0) { cond_velocity_error = fabs(ConductionVelocity - prev_cond_velocity) / prev_cond_velocity; } #define COVERAGE_IGNORE if (prev_apd90_first_qn != 0.0) { apd90_first_qn_error = fabs(Apd90FirstQn - prev_apd90_first_qn) / prev_apd90_first_qn; } if (prev_apd90_third_qn != 0.0) { apd90_third_qn_error = fabs(Apd90ThirdQn - prev_apd90_third_qn) / prev_apd90_third_qn; } if (apd90_first_qn_error == 0.0) { apd90_first_qn_error = DBL_EPSILON; //Avoid log zero on plot } if (apd90_third_qn_error == 0.0) { apd90_third_qn_error = DBL_EPSILON; //Avoid log zero on plot } #undef COVERAGE_IGNORE if (cond_velocity_error == 0.0) { cond_velocity_error = DBL_EPSILON; //Avoid log zero on plot } } prev_cond_velocity = ConductionVelocity; prev_apd90_first_qn = Apd90FirstQn; prev_apd90_third_qn = Apd90ThirdQn; // calculate l2norm double max_abs_error = 0; double sum_sq_abs_error =0; double sum_sq_prev_voltage = 0; double sum_sq_abs_error_full =0; double sum_sq_prev_voltage_full = 0; if (this->PopulatedResult) { //If the PDE step is varying then we'll have twice as much data now as we use to have unsigned time_factor=(time_series.size()-1) / (prev_times.size()-1); assert (time_factor == 1 || time_factor == 2 || time_factor == 8); //Iterate over the shorter time series data for (unsigned data_point = 0; data_point<prev_times.size(); data_point++) { unsigned this_data_point=time_factor*data_point; assert(time_series[this_data_point] == prev_times[data_point]); double abs_error = fabs(transmembrane_potential[this_data_point]-prev_voltage[data_point]); max_abs_error = (abs_error > max_abs_error) ? abs_error : max_abs_error; //Only do resolve the upstroke... sum_sq_abs_error_full += abs_error*abs_error; sum_sq_prev_voltage_full += prev_voltage[data_point] * prev_voltage[data_point]; if (time_series[this_data_point] <= 8.0) { //In most regular cases we always do this, since the simulation stops at ms sum_sq_abs_error += abs_error*abs_error; sum_sq_prev_voltage += prev_voltage[data_point] * prev_voltage[data_point]; } } } if (!this->PopulatedResult || !FixedResult) { prev_voltage = transmembrane_potential; prev_times = time_series; } if (this->PopulatedResult) { double l2_norm_upstroke = sqrt(sum_sq_abs_error/sum_sq_prev_voltage); double l2_norm_full = sqrt(sum_sq_abs_error_full/sum_sq_prev_voltage_full); if (PetscTools::AmMaster()) { (*p_conv_info_file) << std::setprecision(8) << Abscissa() << "\t" << l2_norm_full << "\t" << l2_norm_upstroke << "\t" << max_abs_error << "\t" << Apd90FirstQn <<" "<< apd90_first_qn_error <<""<< "\t" << Apd90ThirdQn <<" "<< apd90_third_qn_error <<""<< "\t" << ConductionVelocity <<" "<< cond_velocity_error <<""<< std::endl; } // convergence criterion this->Converged = l2_norm_full < this->RelativeConvergenceCriterion; this->LastDifference = l2_norm_full; #define COVERAGE_IGNORE assert (time_series.size() != 1u); if (time_series.back() == 0.0) { std::cout << "Failed after successful convergence - give up this convergence test\n"; break; } #undef COVERAGE_IGNORE } if ( time_series.back() != 0.0) { // Simulation ran to completion this->PopulatedResult=true; } } // Get ready for the next test by halving the time step if (!this->Converged) { UpdateConvergenceParameters(); file_num++; } delete p_cell_factory; } while (!GiveUpConvergence() && !this->Converged); if (PetscTools::AmMaster()) { p_conv_info_file->close(); std::cout << "Results: " << std::endl; FileFinder info_finder = conv_info_handler.FindFile(nameOfTest + "_info.csv"); std::ifstream info_file(info_finder.GetAbsolutePath().c_str()); if (info_file) { std::cout << info_file.rdbuf(); info_file.close(); } } }
std::auto_ptr<BoundaryConditionsContainer<DIM,DIM,1> > CellBasedPdeHandler<DIM>::ConstructBoundaryConditionsContainer( PdeAndBoundaryConditions<DIM>* pPdeAndBc, TetrahedralMesh<DIM,DIM>* pMesh) { std::auto_ptr<BoundaryConditionsContainer<DIM,DIM,1> > p_bcc(new BoundaryConditionsContainer<DIM,DIM,1>(false)); AbstractBoundaryCondition<DIM>* p_bc = pPdeAndBc->GetBoundaryCondition(); if (pPdeAndBc->IsNeumannBoundaryCondition()) // this BC is of Neumann type { // Note p_mesh is the coarse mesh or the natural mesh as appropriate for (typename TetrahedralMesh<DIM,DIM>::BoundaryElementIterator elem_iter = pMesh->GetBoundaryElementIteratorBegin(); elem_iter != pMesh->GetBoundaryElementIteratorEnd(); ++elem_iter) { p_bcc->AddNeumannBoundaryCondition(*elem_iter, p_bc); } } else // assume that if the BC is not of Neumann type, then it is Dirichlet { bool using_coarse_pde_mesh = (mpCoarsePdeMesh != NULL); if (using_coarse_pde_mesh && !mSetBcsOnCoarseBoundary) { // Get the set of coarse element indices that contain cells std::set<unsigned> coarse_element_indices_in_map; for (typename AbstractCellPopulation<DIM>::Iterator cell_iter = mpCellPopulation->Begin(); cell_iter != mpCellPopulation->End(); ++cell_iter) { coarse_element_indices_in_map.insert(mCellPdeElementMap[*cell_iter]); } // Find the node indices associated with elements whose indices are NOT in the set coarse_element_indices_in_map std::set<unsigned> coarse_mesh_boundary_node_indices; for (unsigned i=0; i<pMesh->GetNumElements(); i++) { if (coarse_element_indices_in_map.find(i) == coarse_element_indices_in_map.end()) { Element<DIM,DIM>* p_element = pMesh->GetElement(i); for (unsigned j=0; j<DIM+1; j++) { unsigned node_index = p_element->GetNodeGlobalIndex(j); coarse_mesh_boundary_node_indices.insert(node_index); } } } // Apply boundary condition to the nodes in the set coarse_mesh_boundary_node_indices for (std::set<unsigned>::iterator iter = coarse_mesh_boundary_node_indices.begin(); iter != coarse_mesh_boundary_node_indices.end(); ++iter) { p_bcc->AddDirichletBoundaryCondition(pMesh->GetNode(*iter), p_bc, 0, false); } } else // apply BC at boundary nodes of (population-level or coarse) mesh { for (typename TetrahedralMesh<DIM,DIM>::BoundaryNodeIterator node_iter = pMesh->GetBoundaryNodeIteratorBegin(); node_iter != pMesh->GetBoundaryNodeIteratorEnd(); ++node_iter) { p_bcc->AddDirichletBoundaryCondition(*node_iter, p_bc); } } } return p_bcc; }
// In this test we have no cardiac tissue, so that the equations are just sigma * phi_e''=0 // throughout the domain (with a Neumann boundary condition on x=1 and a dirichlet boundary // condition (ie grounding) on x=0), so the exact solution can be calculated and compared // against. void Test1dProblemOnlyBathGroundedOneSide() throw (Exception) { HeartConfig::Instance()->SetSimulationDuration(0.5); //ms HeartConfig::Instance()->SetOutputDirectory("BidomainBathOnlyBath"); HeartConfig::Instance()->SetOutputFilenamePrefix("bidomain_bath"); c_vector<double,1> centre; centre(0) = 0.5; BathCellFactory<1> cell_factory(-1e6, centre); TrianglesMeshReader<1,1> reader("mesh/test/data/1D_0_to_1_10_elements"); TetrahedralMesh<1,1> mesh; mesh.ConstructFromMeshReader(reader); for(unsigned i=0; i<mesh.GetNumElements(); i++) { mesh.GetElement(i)->SetAttribute(HeartRegionCode::GetValidBathId()); } // create boundary conditions container double boundary_val = 1.0; boost::shared_ptr<BoundaryConditionsContainer<1,1,2> > p_bcc(new BoundaryConditionsContainer<1,1,2>); ConstBoundaryCondition<1>* p_bc_stim = new ConstBoundaryCondition<1>(boundary_val); ConstBoundaryCondition<1>* p_zero_stim = new ConstBoundaryCondition<1>(0.0); // loop over boundary elements and set (sigma\gradphi).n = 1.0 on RHS edge for(TetrahedralMesh<1,1>::BoundaryElementIterator iter = mesh.GetBoundaryElementIteratorBegin(); iter != mesh.GetBoundaryElementIteratorEnd(); iter++) { if (((*iter)->GetNodeLocation(0))[0]==1.0) { /// \todo: I think you need to provide a boundary condition for unknown#1 if you are gonig to provide one for unknown#2? p_bcc->AddNeumannBoundaryCondition(*iter, p_zero_stim, 0); p_bcc->AddNeumannBoundaryCondition(*iter, p_bc_stim, 1); } } BidomainWithBathProblem<1> bidomain_problem( &cell_factory ); bidomain_problem.SetBoundaryConditionsContainer(p_bcc); bidomain_problem.SetMesh(&mesh); bidomain_problem.Initialise(); // fix phi=0 on LHS edge std::vector<unsigned> fixed_nodes; fixed_nodes.push_back(0); bidomain_problem.SetFixedExtracellularPotentialNodes(fixed_nodes); bidomain_problem.Solve(); Vec sol = bidomain_problem.GetSolution(); ReplicatableVector sol_repl(sol); // test phi = x*boundary_val/sigma (solution of phi''=0, phi(0)=0, sigma*phi'(1)=boundary_val for(unsigned i=0; i<mesh.GetNumNodes(); i++) { double bath_cond = HeartConfig::Instance()->GetBathConductivity(); double x = mesh.GetNode(i)->rGetLocation()[0]; TS_ASSERT_DELTA(sol_repl[2*i], 0.0, 1e-12); // V TS_ASSERT_DELTA(sol_repl[2*i+1], x*boundary_val/bath_cond, 1e-4); // phi_e } }