// Solve a Purkinje problem and check that the solution for the myocardium nodes is exactly the same as // the solution of an equivalent monodomain-only problem, that the purkinje voltage for // purkinje nodes doesn't change (no purkinje stimulus is given), and is 0 for non-purkinje nodes. void TestMonodomainPurkinjeSolver() { /* The assembly requires 1/time-step, * here we are providing enough information without starting a whole simulation */ PdeSimulationTime::SetTime(0.0); PdeSimulationTime::SetPdeTimeStepAndNextTime(0.01, 0.01); HeartConfig::Instance()->SetUseAbsoluteTolerance(1e-12); HeartConfig::Instance()->SetPurkinjeSurfaceAreaToVolumeRatio(HeartConfig::Instance()->GetSurfaceAreaToVolumeRatio()); std::string mesh_base("mesh/test/data/mixed_dimension_meshes/2D_0_to_1mm_200_elements"); TrianglesMeshReader<2,2> reader(mesh_base); MixedDimensionMesh<2,2> mesh(DistributedTetrahedralMeshPartitionType::DUMB); mesh.ConstructFromMeshReader(reader); //The mixed dimension mesh specifies fibre radii, we override these for the purposes of the //test for (MixedDimensionMesh<2, 2>::CableElementIterator iter = mesh.GetCableElementIteratorBegin(); iter != mesh.GetCableElementIteratorEnd(); ++iter) { (*iter)->SetAttribute(0.56419); //1/sqrt(pi), so that the fibre cross section area is 1.0 } std::string mesh_base2("mesh/test/data/2D_0_to_1mm_200_elements"); TrianglesMeshReader<2,2> reader2(mesh_base2); TetrahedralMesh<2,2> mesh_just_monodomain; mesh_just_monodomain.ConstructFromMeshReader(reader2); PurkinjeCellFactory cell_factory; cell_factory.SetMesh(&mesh); NonPurkinjeCellFactory cell_factory_for_just_monodomain; cell_factory_for_just_monodomain.SetMesh(&mesh_just_monodomain); MonodomainTissue<2> tissue( &cell_factory ); MonodomainTissue<2> tissue_for_just_monodomain( &cell_factory_for_just_monodomain ); // Create an empty BCC - zero Neumann BCs will be applied everywhere BoundaryConditionsContainer<2,2,2> bcc; BoundaryConditionsContainer<2,2,1> bcc_for_just_monodomain; MonodomainPurkinjeSolver<2,2> solver(&mesh, &tissue, &bcc); MonodomainSolver<2,2> solver_just_monodomain(&mesh_just_monodomain, &tissue_for_just_monodomain, &bcc_for_just_monodomain); //Create an initial condition DistributedVectorFactory* p_vector_factory = mesh.GetDistributedVectorFactory(); Vec init_cond = p_vector_factory->CreateVec(2); Vec init_cond_just_monodomain = p_vector_factory->CreateVec(1); // get the voltage stripes DistributedVector ic = mesh.GetDistributedVectorFactory()->CreateDistributedVector(init_cond); DistributedVector ic2 = mesh.GetDistributedVectorFactory()->CreateDistributedVector(init_cond_just_monodomain); DistributedVector::Stripe volume_stripe = DistributedVector::Stripe(ic, 0); DistributedVector::Stripe cable_stripe = DistributedVector::Stripe(ic, 1); for (DistributedVector::Iterator index = ic.Begin(); index != ic.End(); ++index) { volume_stripe[index] = tissue.GetCardiacCell(index.Global)->GetVoltage(); cable_stripe[index] = tissue.GetPurkinjeCell(index.Global)->GetVoltage();//doesn't matter if this is fake // make it zero in the cable stripe for the nodes that are not in purkinje .. } ic.Restore(); for (DistributedVector::Iterator index = ic2.Begin(); index != ic2.End(); ++index) { ic2[index] = tissue.GetCardiacCell(index.Global)->GetVoltage(); } ic2.Restore(); double t_end = 1; solver.SetTimes(0, t_end); solver.SetTimeStep(0.01); solver.SetInitialCondition(init_cond); solver.SetOutputDirectoryAndPrefix("MonodomainPurkinje","results"); solver.SetOutputToTxt(true); solver.SetPrintingTimestepMultiple(10); Vec solution = solver.Solve(); // the following assumes Luo-Rudy!! Vec init_cond_for_just_monodomain = PetscTools::CreateAndSetVec(mesh.GetNumNodes(), -83.853); solver_just_monodomain.SetTimes(0, t_end); solver_just_monodomain.SetTimeStep(0.01); solver_just_monodomain.SetInitialCondition(init_cond_for_just_monodomain); Vec solution_just_monodomain = solver_just_monodomain.Solve(); // Test that certain blocks of the matrix and rhs vector in the monodomain-purkinje solve // match the matrix and vector for a normal monodomain solve Vec& r_purk_rhs = solver.GetLinearSystem()->rGetRhsVector(); Vec& r_mono_rhs = solver_just_monodomain.GetLinearSystem()->rGetRhsVector(); Mat& r_purk_mat = solver.GetLinearSystem()->rGetLhsMatrix(); Mat& r_mono_mat = solver_just_monodomain.GetLinearSystem()->rGetLhsMatrix(); TS_ASSERT_EQUALS(PetscVecTools::GetSize(r_purk_rhs), 2*PetscVecTools::GetSize(r_mono_rhs)); assert(PetscVecTools::GetSize(r_mono_rhs)==mesh.GetNumNodes()); int lo, hi; VecGetOwnershipRange(r_mono_rhs, &lo, &hi); // We don't explicitly test the values of the rhs, as this is also tested by comparing the solutions. // As the system matrices are different, the results won't be identical (but will be close) for the same linear solver tolerance. // for(int i=lo; i<hi; i++) // { // TS_ASSERT_DELTA(PetscVecTools::GetElement(r_purk_rhs, 2*i), PetscVecTools::GetElement(r_mono_rhs, i), 1e-8); // } for(int i=lo; i<hi; i++) { for(unsigned j=0; j<mesh.GetNumNodes(); j++) { // 'top-left' block TS_ASSERT_DELTA(PetscMatTools::GetElement(r_purk_mat, 2*i,2*j), PetscMatTools::GetElement(r_mono_mat, i,j), 1e-8); // 'off-diagonal' blocks TS_ASSERT_DELTA(PetscMatTools::GetElement(r_purk_mat, 2*i,2*j+1), 0.0, 1e-8); TS_ASSERT_DELTA(PetscMatTools::GetElement(r_purk_mat, 2*i+1,2*j), 0.0, 1e-8); } } ReplicatableVector soln_repl(solution); ReplicatableVector soln_mono_repl(solution_just_monodomain); for(unsigned i=0; i<mesh.GetNumNodes(); i++) { if(55<=i && i<=65) // purkinje nodes for this mesh { //The Purkinje domain is a 1D line embedded within the tissue. //It is stimulated in the same way as the tissue domain, therefore //the propagation velocity should be the same (within numerical error). TS_ASSERT_DELTA(soln_repl[2*i+1], soln_repl[2*i], 1e-1); } else { TS_ASSERT_DELTA(soln_repl[2*i+1], 0.0, 1e-4) } TS_ASSERT_DELTA(soln_repl[2*i], soln_mono_repl[i], 1e-5); } HeartConfig::Instance()->SetUseStateVariableInterpolation(true); TS_ASSERT_THROWS_THIS(MonodomainPurkinjeSolver2d bad_solver(&mesh, &tissue, &bcc),"State-variable interpolation is not yet supported with Purkinje"); HeartConfig::Instance()->SetUseStateVariableInterpolation(false); PetscTools::Destroy(solution); PetscTools::Destroy(init_cond); PetscTools::Destroy(init_cond_for_just_monodomain); PetscTools::Destroy(solution_just_monodomain); PetscTools::Destroy(init_cond_just_monodomain); }
void TestMonodomainTissueUsingPurkinjeCellFactory() throw(Exception) { HeartConfig::Instance()->Reset(); TrianglesMeshReader<2,2> reader("mesh/test/data/mixed_dimension_meshes/2D_0_to_1mm_200_elements"); MixedDimensionMesh<2,2> mixed_mesh; mixed_mesh.ConstructFromMeshReader(reader); PurkinjeCellFactory cell_factory; cell_factory.SetMesh(&mixed_mesh); MonodomainTissue<2> tissue( &cell_factory ); TS_ASSERT(tissue.HasPurkinje()); TS_ASSERT_EQUALS(tissue.rGetPurkinjeCellsDistributed().size(), tissue.rGetCellsDistributed().size()); TS_ASSERT_EQUALS(tissue.rGetPurkinjeIionicCacheReplicated().GetSize(), tissue.rGetIionicCacheReplicated().GetSize()); for (AbstractTetrahedralMesh<2,2>::NodeIterator current_node = mixed_mesh.GetNodeIteratorBegin(); current_node != mixed_mesh.GetNodeIteratorEnd(); ++current_node) { unsigned global_index = current_node->GetIndex(); AbstractCardiacCellInterface* p_purkinje_cell = tissue.GetPurkinjeCell(global_index); double y = current_node->rGetLocation()[1]; // cable nodes are on y=0.05 (we don't test by index because indices may be permuted in parallel). if( fabs(y-0.05) < 1e-8 ) { TS_ASSERT(dynamic_cast<CellDiFrancescoNoble1985FromCellML*>(p_purkinje_cell) != NULL); } else { TS_ASSERT(dynamic_cast<FakeBathCell*>(p_purkinje_cell) != NULL); } TS_ASSERT_EQUALS(tissue.rGetPurkinjeCellsDistributed()[global_index-mixed_mesh.GetDistributedVectorFactory()->GetLow()], p_purkinje_cell); } // Test archiving too FileFinder archive_dir("monodomain_tissue_purkinje_archive", RelativeTo::ChasteTestOutput); std::string archive_file = "monodomain_tissue_purkinje.arch"; { // Save to archive ArchiveOpener<boost::archive::text_oarchive, std::ofstream> arch_opener(archive_dir, archive_file); boost::archive::text_oarchive* p_arch = arch_opener.GetCommonArchive(); // Make sure at least one Purkinje cell has a non-initial-condition state variable to compare if (mixed_mesh.GetDistributedVectorFactory()->IsGlobalIndexLocal(0u)) { tissue.GetPurkinjeCell(0u)->SetVoltage(1234.5); } AbstractCardiacTissue<2>* const p_archive_tissue = &tissue; (*p_arch) << p_archive_tissue; } { // Load from archive and compare ArchiveOpener<boost::archive::text_iarchive, std::ifstream> arch_opener(archive_dir, archive_file); boost::archive::text_iarchive* p_arch = arch_opener.GetCommonArchive(); AbstractCardiacTissue<2>* p_tissue; (*p_arch) >> p_tissue; TS_ASSERT(p_tissue->HasPurkinje()); TS_ASSERT_EQUALS(p_tissue->rGetPurkinjeCellsDistributed().size(), tissue.rGetPurkinjeCellsDistributed().size()); TS_ASSERT_EQUALS(p_tissue->rGetPurkinjeIionicCacheReplicated().GetSize(), tissue.rGetPurkinjeIionicCacheReplicated().GetSize()); for (AbstractTetrahedralMesh<2,2>::NodeIterator current_node = p_tissue->mpMesh->GetNodeIteratorBegin(); current_node != p_tissue->mpMesh->GetNodeIteratorEnd(); ++current_node) { unsigned global_index = current_node->GetIndex(); AbstractCardiacCellInterface* p_purkinje_cell = p_tissue->GetPurkinjeCell(global_index); double y = current_node->rGetLocation()[1]; // cable nodes are on y=0.05 (we don't test by index because indices may be permuted in parallel). if( fabs(y-0.05) < 1e-8 ) { TS_ASSERT(dynamic_cast<CellDiFrancescoNoble1985FromCellML*>(p_purkinje_cell) != NULL); } else { TS_ASSERT(dynamic_cast<FakeBathCell*>(p_purkinje_cell) != NULL); } TS_ASSERT_EQUALS(p_purkinje_cell->GetVoltage(), tissue.GetPurkinjeCell(global_index)->GetVoltage()); TS_ASSERT_EQUALS(p_tissue->rGetPurkinjeCellsDistributed()[global_index-p_tissue->mpMesh->GetDistributedVectorFactory()->GetLow()], p_purkinje_cell); } delete p_tissue; } const std::string migration_archive_dir("TestMonodomainTissue/purkinje_migration_archive"); { // Save via MonodomainProblem so we can migrate // Note: from Chaste release 3.1 onward we no longer support Boost 1.33. // The earliest version of Boost supported in 1.34 // Run the test with b=_hostconfig,boost=1-34_5 to save /* scons b=_hostconfig,boost=1-34_5 ts=heart/test/monodomain/TestMonodomainTissue.hpp * */ MonodomainProblem<2> monodomain_problem( &cell_factory ); monodomain_problem.SetMesh(&mixed_mesh); monodomain_problem.Initialise(); TS_ASSERT(monodomain_problem.GetMonodomainTissue()->HasPurkinje()); CardiacSimulationArchiver<MonodomainProblem<2> >::Save(monodomain_problem, migration_archive_dir); } TS_ASSERT_EQUALS(tissue.rGetPurkinjeIionicCacheReplicated().GetSize(), 121u); }