This page is a static archive generated from the test file: TestFibroblastsLiteratePaper.hpp

Two dimensional mouse ventricle with lesion simulation

This is the code that was used to perform the simulation in Mahoney et al. (2016).

Code Walkthrough

#include <cxxtest/TestSuite.h>

These two includes are in this project, and not a standard part of Chaste v3.3

#include "ScarCellFactory.hpp"
#include "ScarConductivityModifier.hpp"

The rest of these includes are standard Chaste files…

#include "GmshMeshReader.hpp"
#include "MonodomainProblem.hpp"
#include "DistributedTetrahedralMesh.hpp"
#include "CellProperties.hpp" // For analysing APs

#include "PetscSetupAndFinalize.hpp"

class TestFibroblasts2d : public CxxTest::TestSuite
{
private:
    /** Width of the 2D domain we're simulating. */
    double mRegionWidth;

    /** Radius of the scar in the centre. */
    double mScarRadius;

    /** Width of the boundary region (myocytes but different conductivity) */
    double mBoundaryWidth;

public:
    void Test2dPropagation() throw (Exception)
    {
#ifdef CHASTE_CVODE
        if (*(CommandLineArguments::Instance()->p_argc) == 1)
        {
            std::cout << "TestFibroblasts takes the following arguments:\n"
                    " Mandatory:\n"
                    " * --scar-conduction-scaling <x> The proportion of the intra-cellular conduction to\n"
                    "                                 apply in the central scar region.\n"
                    "\n"
                    " Optional:\n"
                    " * --use-neutral-cells <true or false>  Whether to use neutral cells OR,\n"
                    " * --use-fibroblasts <true or false>    a fibroblast electrophysiology model,\n"
                    "                                        in the scar region.\n"
                    " * --scar-membrane-area-scaling <x> the scaling factor for membrane area (per unit vol) in scar region\n"
                    " * --boundary-conduction-scaling <x> The proportion of intra-cellular conduction to\n"
                    "                                     apply in the boundary of the scar (still myocytes).\n"
                    " * --scar-shape <x> Whether the scar should be a 'CIRCLE' or a 'SQUARE'\n"
                    "\n"
                    " * --pacing-period <x>  The time between paces applied on x=0 (defaults to 100ms)\n"
                    " * --end-time <x>       How long to perform the simulation for (defaults to pacing period).\n"
                    " * --lesion-pacing      Whether to perform pacing in the centre of the lesion (default false).\n"
                    " * --cut  Whether to introduce a cut with complete conduction block (defaults to false).\n";
            return;
        }

        /**
         * SET OPTIONS FOR THIS RUN
         *
         * These are the defaults.
         */
        double pacing_cycle_length = 100;
        bool use_neutral_cell_model = false;
        bool use_fibroblasts = false;
        bool lesion_pacing = false; // Alternative is remote/sinus pacing.
        ScarShape shape = CIRCLE;
        double scar_membrane_area_scaling = 1.0;
        const std::string output_folder = "FibroblastSims/";

        if (CommandLineArguments::Instance()->OptionExists("--pacing-period"))
        {
            pacing_cycle_length = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--pacing-period");
        }
        double end_time = pacing_cycle_length; // Default end time is pacing cycle length

        if (CommandLineArguments::Instance()->OptionExists("--end-time"))
        {
            end_time = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--end-time");
        }

        if (CommandLineArguments::Instance()->OptionExists("--scar-shape"))
        {
            if (CommandLineArguments::Instance()->GetStringCorrespondingToOption("--scar-shape")=="CIRCLE")
            {
                shape = CIRCLE;
            }
            else if (CommandLineArguments::Instance()->GetStringCorrespondingToOption("--scar-shape")=="SQUARE")
            {
                shape = SQUARE;
            }
            else
            {
                EXCEPTION("The --scar-shape argument should be 'CIRCLE' or 'SQUARE'.");
            }
        }

        // Here we say what proportion of the normal conductivity we want in the scar
        double scaling_factor;
        if (CommandLineArguments::Instance()->OptionExists("--scar-conduction-scaling"))
        {
            scaling_factor = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--scar-conduction-scaling");
            assert(scaling_factor >= 0.0);
            assert(scaling_factor <= 1.0);
        }
        else
        {
            EXCEPTION("Please provide the command line option '--scar-conduction-scaling' with a value between 0 and 1.");
        }

        double scaling_factor_boundary = 1.0;
        if (CommandLineArguments::Instance()->OptionExists("--boundary-conduction-scaling"))
        {
            scaling_factor_boundary = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--boundary-conduction-scaling");
            assert(scaling_factor_boundary >= 0.0);
            assert(scaling_factor_boundary <= 1.0);
        }

        // And decide whether to use a neutral cell model, or normal mytocytes.
        if (CommandLineArguments::Instance()->OptionExists("--use-neutral-cells"))
        {
            use_neutral_cell_model = CommandLineArguments::Instance()->GetBoolCorrespondingToOption("--use-neutral-cells");
        }
        // And decide whether to use a fibroblast model
        if (CommandLineArguments::Instance()->OptionExists("--use-fibroblasts"))
        {
            use_fibroblasts = CommandLineArguments::Instance()->GetBoolCorrespondingToOption("--use-fibroblasts");
        }
        if (use_neutral_cell_model && use_fibroblasts)
        {
            EXCEPTION("You can only set '--use-neutral-cells true' OR '--use-fibroblasts true', not both.");
        }

        if (CommandLineArguments::Instance()->OptionExists("--scar-membrane-area-scaling"))
        {
            scar_membrane_area_scaling = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--scar-membrane-area-scaling");
            assert(scar_membrane_area_scaling >= 0.0);
            assert(scar_membrane_area_scaling <= 1.0);
        }

        bool create_cut = false;
        if (CommandLineArguments::Instance()->OptionExists("--cut"))
        {
            create_cut = true;
        }

        if (CommandLineArguments::Instance()->OptionExists("--lesion-pacing"))
        {
            lesion_pacing = true;
        }

        // Set up a unique output folder for these options
        std::stringstream sub_directory;
        if (use_neutral_cell_model)
        {
            sub_directory << "Neutral";
        }
        else if (use_fibroblasts)
        {
            sub_directory << "Fibroblast";
        }
        else
        {
            sub_directory << "Myocyte";
        }

        sub_directory << "_scar_cond_" << scaling_factor << "_cap_" << scar_membrane_area_scaling << "_boundary_cond_" << scaling_factor_boundary << "_period_" << pacing_cycle_length << "_end_" << end_time;

        if (shape==SQUARE)
        {
            sub_directory << "_Square";
        }

        if (create_cut)
        {
            sub_directory << "_WithCut";
        }

        if (lesion_pacing)
        {
            sub_directory << "_LesionPacing";
        }

SET UP MESH

        DistributedTetrahedralMesh<2,2> mesh;

        if (create_cut)
        {
            GmshMeshReader<2,2> gmsh_reader("projects/MouseLesion/test/data/meshes/2D_with_cuts_7_by_9_mm.msh");
            mesh.ConstructFromMeshReader(gmsh_reader);
            // Mesh properties we will use in the cell factory and conductivity modifier.
            mRegionWidth = 0.7; //cm
            mScarRadius = 0.2325; //cm
            mBoundaryWidth = 0.01; //cm
        }
        else
        {
            // Mesh properties we will use in the cell factory and conductivity modifier.
            mRegionWidth = 0.5; //cm
            mScarRadius = 0.1; //cm
            mBoundaryWidth = 0.01; //cm
            double h=0.005;
            mesh.ConstructRegularSlabMesh(h, mRegionWidth, mRegionWidth);
        }

SET STANDARD OPTIONS

        HeartConfig::Instance()->SetSimulationDuration(end_time); //ms
        HeartConfig::Instance()->SetOutputDirectory(output_folder + sub_directory.str());
        HeartConfig::Instance()->SetOutputFilenamePrefix("results");
        HeartConfig::Instance()->SetVisualizeWithVtk(true);

NUMERICAL METHOD PARAMETERS

        HeartConfig::Instance()->SetOdePdeAndPrintingTimeSteps(0.1, 0.1, 0.1);

SET UP HETEROGENEOUS CONDUCTIVITY

        double intra_conductivity = 1.75; // Chaste Defaults
        double extra_conductivity = 7.0; // Chaste Defaults

        // Set up the defaults.
        HeartConfig::Instance()->SetIntracellularConductivities(Create_c_vector(intra_conductivity, intra_conductivity));
        HeartConfig::Instance()->SetExtracellularConductivities(Create_c_vector(extra_conductivity, extra_conductivity));

COMPLETE THE SET UP OF PROBLEM

        // Make a scar cell factory
        ScarCellFactory<2> cell_factory(mRegionWidth,
                                        mScarRadius,
                                        pacing_cycle_length,
                                        scar_membrane_area_scaling,  // chi scaling factor.
                                        use_neutral_cell_model,
                                        use_fibroblasts,
                                        shape,
                                        lesion_pacing,
                                        create_cut);

        // Create a conductivity modifier
        ScarConductivityModifier<2> modifier(&mesh,
                                             shape,
                                             mRegionWidth,
                                             mScarRadius,
                                             scaling_factor, // D scaling factor.
                                             scar_membrane_area_scaling,  // chi scaling factor.
                                             mBoundaryWidth,
                                             scaling_factor_boundary, // D scaling factor (chi not implemented for boundary yet).
                                             true, // whether a lesion applied
                                             create_cut);

        MonodomainProblem<2> problem( &cell_factory );
        problem.SetMesh( &mesh );

        problem.SetWriteInfo(); // Writes max and min voltages out.
        problem.Initialise();
        problem.GetTissue()->SetConductivityModifier(&modifier);

        // Check that the Scar cell factory is doing the index tracking I expect.
        // These should have been calculated during the initialisation step.
        // These were checked in Paraview for the h = 0.005 slab mesh.
        // If these lines fail it isn't a problem if you have intentionally
        // changed the mesh for some reason (as we have when there's a cut).
        if (!create_cut)
        {
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexCentreOfScar(), 5100u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexCentralTissue(), 8130u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexLeftTissue(), 5070u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexRightTissue(), 5130u);
        }

        problem.Solve();

        // Print out a couple of node traces to file for easy plotting.
        // Output file handler should be called collectively, the 'false' says "don't wipe the folder!"
        OutputFileHandler handler(output_folder + sub_directory.str(), false);

        // These methods must be called in parallel.
        std::vector<unsigned> node_indices;
        node_indices.push_back(cell_factory.GetNodeIndexCentreOfScar()); // 0
        node_indices.push_back(cell_factory.GetNodeIndexCentralTissue()); // 1 - best "control" half way through
        node_indices.push_back(cell_factory.GetNodeIndexLeftTissue()); // 2
        node_indices.push_back(cell_factory.GetNodeIndexRightTissue()); // 3
        node_indices.push_back(cell_factory.GetNodeIndexLeftOfScar()); // 4
        node_indices.push_back(cell_factory.GetNodeIndexRightOfScar()); // 5
        node_indices.push_back(cell_factory.GetNodeIndexSideOfScar()); // 6
        node_indices.push_back(cell_factory.GetNodeIndexBaseOfTissue()); // 7

        // But we only want the master to write out the actual file.
        if (PetscTools::AmMaster())
        {
            FileFinder hdf5_dir(output_folder + sub_directory.str(), RelativeTo::ChasteTestOutput);
            Hdf5DataReader reader(hdf5_dir, "results");

            // Retrieve the voltage traces at nodes of interest
            std::vector<std::vector<double> > voltage_traces;
            for (unsigned i=0; i<node_indices.size(); i++)
            {
                voltage_traces.push_back(reader.GetVariableOverTime("V", node_indices[i]));
            }

            // Output the full voltage traces at these nodes
            std::vector<double> times = reader.GetUnlimitedDimensionValues();
            out_stream output_file = handler.OpenOutputFile("voltage_traces_at_selected_nodes.dat");

            for (unsigned i=0; i<times.size(); i++)
            {
                *output_file << times[i];
                for (unsigned j=0; j<node_indices.size(); j++)
                {
                    *output_file << "\t" << voltage_traces[j][i] ;
                }
                *output_file << "\n";
            }
            output_file->close();

            // Also output some of our action potential summary statistics
            output_file = handler.OpenOutputFile("action_potential_properties_at_selected_nodes.dat");
            *output_file << "Node\tAPD90(ms)\tAPD70(ms)\tAPD50(ms)\tMaxUpstroke(mV/ms)\tPeakVoltage(mV)\tAmplitude(mV)\tResting(mV)" << std::endl;
            const double voltage_AP_threshold = -70.0;
            for (unsigned i=0; i<node_indices.size(); i++)
            {
                CellProperties voltage_properties(voltage_traces[i], times, voltage_AP_threshold);
                double apd90, apd70, apd50, upstroke, peak, amplitude, resting;
                try
                {
                    apd90 = voltage_properties.GetLastActionPotentialDuration(90);
                    apd70 = voltage_properties.GetLastActionPotentialDuration(70);
                    apd50 = voltage_properties.GetLastActionPotentialDuration(50);
                    upstroke = voltage_properties.GetLastCompleteMaxUpstrokeVelocity();
                    peak = voltage_properties.GetLastCompletePeakPotential();
                    amplitude = voltage_properties.GetLastActionPotentialAmplitude();
                    resting = voltage_properties.GetRestingPotentials().back();
                }
                catch (Exception& e)
                {   // In case there is no action potential at all.
                    apd90 = 0.0;
                    apd70 = 0.0;
                    apd50 = 0.0;
                    upstroke = 0.0;
                    peak = 0.0;
                    amplitude = 0.0;
                    resting = voltage_traces[i].back();
                }
                *output_file << i << "\t"
                             << apd90 << "\t" << apd70 << "\t" << apd50 << "\t"
                             << upstroke << "\t" << peak << "\t" << amplitude << "\t" << resting << std::endl;
            }
            output_file->close();
        }

#else
        std::cout << "CVODE is not installed, or CHASTE is not configured to use it, check your hostconfig settings." << std::endl;
        // TS_ASSERT(false); // uncomment if you want to ensure CVODE is set up on your system.
#endif // CHASTE_CVODE
    }
};

Full code

TestFibroblastsLiteratePaper.hpp
#include <cxxtest/TestSuite.h>

#include "ScarCellFactory.hpp"
#include "ScarConductivityModifier.hpp"

#include "GmshMeshReader.hpp"
#include "MonodomainProblem.hpp"
#include "DistributedTetrahedralMesh.hpp"
#include "CellProperties.hpp" // For analysing APs

#include "PetscSetupAndFinalize.hpp"

class TestFibroblasts2d : public CxxTest::TestSuite
{
private:
    /** Width of the 2D domain we're simulating. */
    double mRegionWidth;

    /** Radius of the scar in the centre. */
    double mScarRadius;

    /** Width of the boundary region (myocytes but different conductivity) */
    double mBoundaryWidth;

public:
    void Test2dPropagation() throw (Exception)
    {
#ifdef CHASTE_CVODE
        if (*(CommandLineArguments::Instance()->p_argc) == 1)
        {
            std::cout << "TestFibroblasts takes the following arguments:\n"
                    " Mandatory:\n"
                    " * --scar-conduction-scaling <x> The proportion of the intra-cellular conduction to\n"
                    "                                 apply in the central scar region.\n"
                    "\n"
                    " Optional:\n"
                    " * --use-neutral-cells <true or false>  Whether to use neutral cells OR,\n"
                    " * --use-fibroblasts <true or false>    a fibroblast electrophysiology model,\n"
                    "                                        in the scar region.\n"
                    " * --scar-membrane-area-scaling <x> the scaling factor for membrane area (per unit vol) in scar region\n"
                    " * --boundary-conduction-scaling <x> The proportion of intra-cellular conduction to\n"
                    "                                     apply in the boundary of the scar (still myocytes).\n"
                    " * --scar-shape <x> Whether the scar should be a 'CIRCLE' or a 'SQUARE'\n"
                    "\n"
                    " * --pacing-period <x>  The time between paces applied on x=0 (defaults to 100ms)\n"
                    " * --end-time <x>       How long to perform the simulation for (defaults to pacing period).\n"
                    " * --lesion-pacing      Whether to perform pacing in the centre of the lesion (default false).\n"
                    " * --cut  Whether to introduce a cut with complete conduction block (defaults to false).\n";
            return;
        }

        /**
         * SET OPTIONS FOR THIS RUN
         *
         * These are the defaults.
         */
        double pacing_cycle_length = 100;
        bool use_neutral_cell_model = false;
        bool use_fibroblasts = false;
        bool lesion_pacing = false; // Alternative is remote/sinus pacing.
        ScarShape shape = CIRCLE;
        double scar_membrane_area_scaling = 1.0;
        const std::string output_folder = "FibroblastSims/";

        if (CommandLineArguments::Instance()->OptionExists("--pacing-period"))
        {
            pacing_cycle_length = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--pacing-period");
        }
        double end_time = pacing_cycle_length; // Default end time is pacing cycle length

        if (CommandLineArguments::Instance()->OptionExists("--end-time"))
        {
            end_time = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--end-time");
        }

        if (CommandLineArguments::Instance()->OptionExists("--scar-shape"))
        {
            if (CommandLineArguments::Instance()->GetStringCorrespondingToOption("--scar-shape")=="CIRCLE")
            {
                shape = CIRCLE;
            }
            else if (CommandLineArguments::Instance()->GetStringCorrespondingToOption("--scar-shape")=="SQUARE")
            {
                shape = SQUARE;
            }
            else
            {
                EXCEPTION("The --scar-shape argument should be 'CIRCLE' or 'SQUARE'.");
            }
        }

        // Here we say what proportion of the normal conductivity we want in the scar
        double scaling_factor;
        if (CommandLineArguments::Instance()->OptionExists("--scar-conduction-scaling"))
        {
            scaling_factor = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--scar-conduction-scaling");
            assert(scaling_factor >= 0.0);
            assert(scaling_factor <= 1.0);
        }
        else
        {
            EXCEPTION("Please provide the command line option '--scar-conduction-scaling' with a value between 0 and 1.");
        }

        double scaling_factor_boundary = 1.0;
        if (CommandLineArguments::Instance()->OptionExists("--boundary-conduction-scaling"))
        {
            scaling_factor_boundary = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--boundary-conduction-scaling");
            assert(scaling_factor_boundary >= 0.0);
            assert(scaling_factor_boundary <= 1.0);
        }

        // And decide whether to use a neutral cell model, or normal mytocytes.
        if (CommandLineArguments::Instance()->OptionExists("--use-neutral-cells"))
        {
            use_neutral_cell_model = CommandLineArguments::Instance()->GetBoolCorrespondingToOption("--use-neutral-cells");
        }
        // And decide whether to use a fibroblast model
        if (CommandLineArguments::Instance()->OptionExists("--use-fibroblasts"))
        {
            use_fibroblasts = CommandLineArguments::Instance()->GetBoolCorrespondingToOption("--use-fibroblasts");
        }
        if (use_neutral_cell_model && use_fibroblasts)
        {
            EXCEPTION("You can only set '--use-neutral-cells true' OR '--use-fibroblasts true', not both.");
        }

        if (CommandLineArguments::Instance()->OptionExists("--scar-membrane-area-scaling"))
        {
            scar_membrane_area_scaling = CommandLineArguments::Instance()->GetDoubleCorrespondingToOption("--scar-membrane-area-scaling");
            assert(scar_membrane_area_scaling >= 0.0);
            assert(scar_membrane_area_scaling <= 1.0);
        }

        bool create_cut = false;
        if (CommandLineArguments::Instance()->OptionExists("--cut"))
        {
            create_cut = true;
        }

        if (CommandLineArguments::Instance()->OptionExists("--lesion-pacing"))
        {
            lesion_pacing = true;
        }

        // Set up a unique output folder for these options
        std::stringstream sub_directory;
        if (use_neutral_cell_model)
        {
            sub_directory << "Neutral";
        }
        else if (use_fibroblasts)
        {
            sub_directory << "Fibroblast";
        }
        else
        {
            sub_directory << "Myocyte";
        }

        sub_directory << "_scar_cond_" << scaling_factor << "_cap_" << scar_membrane_area_scaling << "_boundary_cond_" << scaling_factor_boundary << "_period_" << pacing_cycle_length << "_end_" << end_time;

        if (shape==SQUARE)
        {
            sub_directory << "_Square";
        }

        if (create_cut)
        {
            sub_directory << "_WithCut";
        }

        if (lesion_pacing)
        {
            sub_directory << "_LesionPacing";
        }

        DistributedTetrahedralMesh<2,2> mesh;

        if (create_cut)
        {
            GmshMeshReader<2,2> gmsh_reader("projects/MouseLesion/test/data/meshes/2D_with_cuts_7_by_9_mm.msh");
            mesh.ConstructFromMeshReader(gmsh_reader);
            // Mesh properties we will use in the cell factory and conductivity modifier.
            mRegionWidth = 0.7; //cm
            mScarRadius = 0.2325; //cm
            mBoundaryWidth = 0.01; //cm
        }
        else
        {
            // Mesh properties we will use in the cell factory and conductivity modifier.
            mRegionWidth = 0.5; //cm
            mScarRadius = 0.1; //cm
            mBoundaryWidth = 0.01; //cm
            double h=0.005;
            mesh.ConstructRegularSlabMesh(h, mRegionWidth, mRegionWidth);
        }

        HeartConfig::Instance()->SetSimulationDuration(end_time); //ms
        HeartConfig::Instance()->SetOutputDirectory(output_folder + sub_directory.str());
        HeartConfig::Instance()->SetOutputFilenamePrefix("results");
        HeartConfig::Instance()->SetVisualizeWithVtk(true);

        HeartConfig::Instance()->SetOdePdeAndPrintingTimeSteps(0.1, 0.1, 0.1);

        double intra_conductivity = 1.75; // Chaste Defaults
        double extra_conductivity = 7.0; // Chaste Defaults

        // Set up the defaults.
        HeartConfig::Instance()->SetIntracellularConductivities(Create_c_vector(intra_conductivity, intra_conductivity));
        HeartConfig::Instance()->SetExtracellularConductivities(Create_c_vector(extra_conductivity, extra_conductivity));

        // Make a scar cell factory
        ScarCellFactory<2> cell_factory(mRegionWidth,
                                        mScarRadius,
                                        pacing_cycle_length,
                                        scar_membrane_area_scaling,  // chi scaling factor.
                                        use_neutral_cell_model,
                                        use_fibroblasts,
                                        shape,
                                        lesion_pacing,
                                        create_cut);

        // Create a conductivity modifier
        ScarConductivityModifier<2> modifier(&mesh,
                                             shape,
                                             mRegionWidth,
                                             mScarRadius,
                                             scaling_factor, // D scaling factor.
                                             scar_membrane_area_scaling,  // chi scaling factor.
                                             mBoundaryWidth,
                                             scaling_factor_boundary, // D scaling factor (chi not implemented for boundary yet).
                                             true, // whether a lesion applied
                                             create_cut);

        MonodomainProblem<2> problem( &cell_factory );
        problem.SetMesh( &mesh );

        problem.SetWriteInfo(); // Writes max and min voltages out.
        problem.Initialise();
        problem.GetTissue()->SetConductivityModifier(&modifier);

        // Check that the Scar cell factory is doing the index tracking I expect.
        // These should have been calculated during the initialisation step.
        // These were checked in Paraview for the h = 0.005 slab mesh.
        // If these lines fail it isn't a problem if you have intentionally
        // changed the mesh for some reason (as we have when there's a cut).
        if (!create_cut)
        {
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexCentreOfScar(), 5100u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexCentralTissue(), 8130u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexLeftTissue(), 5070u);
            TS_ASSERT_EQUALS(cell_factory.GetNodeIndexRightTissue(), 5130u);
        }

        problem.Solve();

        // Print out a couple of node traces to file for easy plotting.
        // Output file handler should be called collectively, the 'false' says "don't wipe the folder!"
        OutputFileHandler handler(output_folder + sub_directory.str(), false);

        // These methods must be called in parallel.
        std::vector<unsigned> node_indices;
        node_indices.push_back(cell_factory.GetNodeIndexCentreOfScar()); // 0
        node_indices.push_back(cell_factory.GetNodeIndexCentralTissue()); // 1 - best "control" half way through
        node_indices.push_back(cell_factory.GetNodeIndexLeftTissue()); // 2
        node_indices.push_back(cell_factory.GetNodeIndexRightTissue()); // 3
        node_indices.push_back(cell_factory.GetNodeIndexLeftOfScar()); // 4
        node_indices.push_back(cell_factory.GetNodeIndexRightOfScar()); // 5
        node_indices.push_back(cell_factory.GetNodeIndexSideOfScar()); // 6
        node_indices.push_back(cell_factory.GetNodeIndexBaseOfTissue()); // 7

        // But we only want the master to write out the actual file.
        if (PetscTools::AmMaster())
        {
            FileFinder hdf5_dir(output_folder + sub_directory.str(), RelativeTo::ChasteTestOutput);
            Hdf5DataReader reader(hdf5_dir, "results");

            // Retrieve the voltage traces at nodes of interest
            std::vector<std::vector<double> > voltage_traces;
            for (unsigned i=0; i<node_indices.size(); i++)
            {
                voltage_traces.push_back(reader.GetVariableOverTime("V", node_indices[i]));
            }

            // Output the full voltage traces at these nodes
            std::vector<double> times = reader.GetUnlimitedDimensionValues();
            out_stream output_file = handler.OpenOutputFile("voltage_traces_at_selected_nodes.dat");

            for (unsigned i=0; i<times.size(); i++)
            {
                *output_file << times[i];
                for (unsigned j=0; j<node_indices.size(); j++)
                {
                    *output_file << "\t" << voltage_traces[j][i] ;
                }
                *output_file << "\n";
            }
            output_file->close();

            // Also output some of our action potential summary statistics
            output_file = handler.OpenOutputFile("action_potential_properties_at_selected_nodes.dat");
            *output_file << "Node\tAPD90(ms)\tAPD70(ms)\tAPD50(ms)\tMaxUpstroke(mV/ms)\tPeakVoltage(mV)\tAmplitude(mV)\tResting(mV)" << std::endl;
            const double voltage_AP_threshold = -70.0;
            for (unsigned i=0; i<node_indices.size(); i++)
            {
                CellProperties voltage_properties(voltage_traces[i], times, voltage_AP_threshold);
                double apd90, apd70, apd50, upstroke, peak, amplitude, resting;
                try
                {
                    apd90 = voltage_properties.GetLastActionPotentialDuration(90);
                    apd70 = voltage_properties.GetLastActionPotentialDuration(70);
                    apd50 = voltage_properties.GetLastActionPotentialDuration(50);
                    upstroke = voltage_properties.GetLastCompleteMaxUpstrokeVelocity();
                    peak = voltage_properties.GetLastCompletePeakPotential();
                    amplitude = voltage_properties.GetLastActionPotentialAmplitude();
                    resting = voltage_properties.GetRestingPotentials().back();
                }
                catch (Exception& e)
                {   // In case there is no action potential at all.
                    apd90 = 0.0;
                    apd70 = 0.0;
                    apd50 = 0.0;
                    upstroke = 0.0;
                    peak = 0.0;
                    amplitude = 0.0;
                    resting = voltage_traces[i].back();
                }
                *output_file << i << "\t"
                             << apd90 << "\t" << apd70 << "\t" << apd50 << "\t"
                             << upstroke << "\t" << peak << "\t" << amplitude << "\t" << resting << std::endl;
            }
            output_file->close();
        }

#else
        std::cout << "CVODE is not installed, or CHASTE is not configured to use it, check your hostconfig settings." << std::endl;
        // TS_ASSERT(false); // uncomment if you want to ensure CVODE is set up on your system.
#endif // CHASTE_CVODE
    }
};