void AbstractCardiacProblem<ELEMENT_DIM,SPACE_DIM,PROBLEM_DIM>::Solve()
{
    PreSolveChecks();

    std::vector<double> additional_stopping_times;
    SetUpAdditionalStoppingTimes(additional_stopping_times);

    TimeStepper stepper(mCurrentTime,
                        HeartConfig::Instance()->GetSimulationDuration(),
                        HeartConfig::Instance()->GetPrintingTimeStep(),
                        false,
                        additional_stopping_times);
    // Note that SetUpAdditionalStoppingTimes is a method from the BidomainWithBath class it adds
    // electrode events into the regular time-stepping
    //    EXCEPTION("Electrode switch on/off events should coincide with printing time steps.");

    if (!mpBoundaryConditionsContainer) // the user didn't supply a bcc
    {
        // Set up the default bcc
        mpDefaultBoundaryConditionsContainer.reset(new BoundaryConditionsContainer<ELEMENT_DIM, SPACE_DIM, PROBLEM_DIM>);
        for (unsigned problem_index=0; problem_index<PROBLEM_DIM; problem_index++)
        {
            mpDefaultBoundaryConditionsContainer->DefineZeroNeumannOnMeshBoundary(mpMesh, problem_index);
        }
        mpBoundaryConditionsContainer = mpDefaultBoundaryConditionsContainer;
    }

    assert(mpSolver==NULL);
    mpSolver = CreateSolver(); // passes mpBoundaryConditionsContainer to solver

    // If we have already run a simulation, use the old solution as initial condition
    Vec initial_condition;
    if (mSolution)
    {
        initial_condition = mSolution;
    }
    else
    {
        initial_condition = CreateInitialCondition();
    }

    std::string progress_reporter_dir;

    if (mPrintOutput)
    {
        HeartEventHandler::BeginEvent(HeartEventHandler::WRITE_OUTPUT);
        bool extending_file = false;
        try
        {
            extending_file = InitialiseWriter();
        }
        catch (Exception& e)
        {
            delete mpWriter;
            mpWriter = NULL;
            delete mpSolver;
            if (mSolution != initial_condition)
            {
                /*
                 * A PETSc Vec is a pointer, so we *don't* need to free the memory if it is
                 * freed somewhere else (e.g. in the destructor). If this is a resumed solution
                 * we set initial_condition = mSolution earlier. mSolution is going to be
                 * cleaned up in the constructor. So, only PetscTools::Destroy( initial_condition ) when
                 * it is not equal to mSolution.
                 */
                PetscTools::Destroy(initial_condition);
            }
            throw e;
        }

        /*
         * If we are resuming a simulation (i.e. mSolution already exists) and
         * we are extending a .h5 file that already exists then there is no need
         * to write the initial condition to file - it is already there as the
         * final solution of the previous run.
         */
        if (!(mSolution && extending_file))
        {
            WriteOneStep(stepper.GetTime(), initial_condition);
            mpWriter->AdvanceAlongUnlimitedDimension();
        }
        HeartEventHandler::EndEvent(HeartEventHandler::WRITE_OUTPUT);

        progress_reporter_dir = HeartConfig::Instance()->GetOutputDirectory();
    }
    else
    {
        progress_reporter_dir = ""; // progress printed to CHASTE_TEST_OUTPUT
    }
    BOOST_FOREACH(boost::shared_ptr<AbstractOutputModifier> p_output_modifier, mOutputModifiers)
    {
        p_output_modifier->InitialiseAtStart(this->mpMesh->GetDistributedVectorFactory());
        p_output_modifier->ProcessSolutionAtTimeStep(stepper.GetTime(), initial_condition, PROBLEM_DIM);
    }
Vec AbstractDynamicLinearPdeSolver<ELEMENT_DIM, SPACE_DIM, PROBLEM_DIM>::Solve()
{
    // Begin by checking that everything has been set up correctly
    if (!mTimesSet)
    {
        EXCEPTION("SetTimes() has not been called");
    }
    if ((mIdealTimeStep <= 0.0) && (mpTimeAdaptivityController==NULL))
    {
        EXCEPTION("SetTimeStep() has not been called");
    }
    if (mInitialCondition == NULL)
    {
        EXCEPTION("SetInitialCondition() has not been called");
    }

    // If required, initialise HDF5 writer and output initial condition to HDF5 file
    bool print_output = (mOutputToVtk || mOutputToParallelVtk || mOutputToTxt);
    if (print_output)
    {
        InitialiseHdf5Writer();
        WriteOneStep(mTstart, mInitialCondition);
        mpHdf5Writer->AdvanceAlongUnlimitedDimension();
    }

    this->InitialiseForSolve(mInitialCondition);

    if (mIdealTimeStep < 0) // hasn't been set, so a controller must have been given
    {
        mIdealTimeStep = mpTimeAdaptivityController->GetNextTimeStep(mTstart, mInitialCondition);
    }

    /*
     * Note: we use the mIdealTimeStep here (the original timestep that was passed in, or
     * the last timestep suggested by the controller), rather than the last timestep used
     * (mLastWorkingTimeStep), because the timestep will be very slightly altered by the
     * stepper in the final timestep of the last printing-timestep-loop, and these floating
     * point errors can add up and eventually cause exceptions being thrown.
     */
    TimeStepper stepper(mTstart, mTend, mIdealTimeStep, mMatrixIsConstant);

    Vec solution = mInitialCondition;
    Vec next_solution;

    while (!stepper.IsTimeAtEnd())
    {
        bool timestep_changed = false;

        PdeSimulationTime::SetTime(stepper.GetTime());

        // Determine timestep to use
        double new_dt;
        if (mpTimeAdaptivityController)
        {
            // Get the timestep the controller wants to use and store it as the ideal timestep
            mIdealTimeStep = mpTimeAdaptivityController->GetNextTimeStep(stepper.GetTime(), solution);

            // Tell the stepper to use this timestep from now on...
            stepper.ResetTimeStep(mIdealTimeStep);

            // ..but now get the timestep from the stepper, as the stepper might need
            // to trim the timestep if it would take us over the end time
            new_dt = stepper.GetNextTimeStep();
            // Changes in timestep bigger than 0.001% will trigger matrix re-computation
            timestep_changed = (fabs(new_dt/mLastWorkingTimeStep - 1.0) > 1e-5);
        }
        else
        {
            new_dt = stepper.GetNextTimeStep();

            //new_dt should be roughly the same size as mIdealTimeStep - we should never need to take a tiny step

            if (mMatrixIsConstant && fabs(new_dt/mIdealTimeStep - 1.0) > 1e-5)
            {
                // Here we allow for changes of up to 0.001%
                // Note that the TimeStepper guarantees that changes in dt are no bigger than DBL_EPSILON*current_time
                NEVER_REACHED;
            }
        }

        // Save the timestep as the last one use, and also put it in PdeSimulationTime
        // so everyone can see it
        mLastWorkingTimeStep = new_dt;
        PdeSimulationTime::SetPdeTimeStepAndNextTime(new_dt, stepper.GetNextTime());

        // Solve
        try
        {
            // (This runs the cell ODE models in heart simulations)
            this->PrepareForSetupLinearSystem(solution);
        }
        catch(Exception& e)
        {
            // We only need to clean up memory if we are NOT on the first PDE time step,
            // as someone else cleans up the mInitialCondition vector in higher classes.
            if (solution != mInitialCondition)
            {
                HeartEventHandler::BeginEvent(HeartEventHandler::COMMUNICATION);
                PetscTools::Destroy(solution);
                HeartEventHandler::EndEvent(HeartEventHandler::COMMUNICATION);
            }
            throw e;
        }

        bool compute_matrix = (!mMatrixIsConstant || !mMatrixIsAssembled || timestep_changed);

        this->SetupLinearSystem(solution, compute_matrix);

        this->FinaliseLinearSystem(solution);

        if (compute_matrix)
        {
            this->mpLinearSystem->ResetKspSolver();
        }

        next_solution = this->mpLinearSystem->Solve(solution);

        if (mMatrixIsConstant)
        {
            mMatrixIsAssembled = true;
        }

        this->FollowingSolveLinearSystem(next_solution);

        stepper.AdvanceOneTimeStep();

        // Avoid memory leaks
        if (solution != mInitialCondition)
        {
            HeartEventHandler::BeginEvent(HeartEventHandler::COMMUNICATION);
            PetscTools::Destroy(solution);
            HeartEventHandler::EndEvent(HeartEventHandler::COMMUNICATION);
        }
        solution = next_solution;

        // If required, output next solution to HDF5 file
        if (print_output && (stepper.GetTotalTimeStepsTaken()%mPrintingTimestepMultiple == 0) )
        {
            WriteOneStep(stepper.GetTime(), solution);
            mpHdf5Writer->AdvanceAlongUnlimitedDimension();
        }
    }

    // Avoid memory leaks
    if (mpHdf5Writer != NULL)
    {
        delete mpHdf5Writer;
        mpHdf5Writer = NULL;
    }

    // Convert HDF5 output to other formats as required

    if (mOutputToVtk)
    {
        Hdf5ToVtkConverter<ELEMENT_DIM,SPACE_DIM> converter(FileFinder(mOutputDirectory, RelativeTo::ChasteTestOutput),
                                                            mFilenamePrefix, this->mpMesh, false, false);
    }
    if (mOutputToParallelVtk)
    {
        Hdf5ToVtkConverter<ELEMENT_DIM,SPACE_DIM> converter(FileFinder(mOutputDirectory, RelativeTo::ChasteTestOutput),
                                                            mFilenamePrefix, this->mpMesh, true, false);
    }
    if (mOutputToTxt)
    {
        Hdf5ToTxtConverter<ELEMENT_DIM,SPACE_DIM> converter(FileFinder(mOutputDirectory, RelativeTo::ChasteTestOutput),
                                                            mFilenamePrefix, this->mpMesh);
    }

    return solution;
}