// 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);
    }
Esempio n. 2
0
    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);
    }