///////////////////////////////////////////////////////////////////////////////
//
// File: SessionReader.cpp
//
// For more information, please see: http://www.nektar.info
//
// The MIT License
//
// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
// Department of Aeronautics, Imperial College London (UK), and Scientific
// Computing and Imaging Institute, University of Utah (USA).
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// Description: Session reader
//
///////////////////////////////////////////////////////////////////////////////

#ifndef TIXML_USE_STL
#define TIXML_USE_STL
#endif

#include <LibUtilities/BasicConst/GitRevision.h>
#include <LibUtilities/BasicUtils/SessionReader.h>

#include <fstream>
#include <iostream>
#include <string>

#include <boost/algorithm/string.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>

#include <tinyxml.h>

#include <LibUtilities/BasicUtils/CheckedCast.hpp>
#include <LibUtilities/BasicUtils/Equation.h>
#include <LibUtilities/BasicUtils/ErrorUtil.hpp>
#include <LibUtilities/BasicUtils/Filesystem.hpp>
#include <LibUtilities/BasicUtils/ParseUtils.h>
#include <LibUtilities/Interpreter/Interpreter.h>
#include <LibUtilities/Memory/NekMemoryManager.hpp>

#include <boost/format.hpp>
#include <boost/program_options.hpp>

#ifndef NEKTAR_VERSION
#define NEKTAR_VERSION "Unknown"
#endif

using namespace std;

namespace po = boost::program_options;
namespace io = boost::iostreams;

namespace Nektar::LibUtilities
{
/**
 * @class SessionReader
 *
 * This class provides an interface to Nektar++-specific content in a
 * supplied XML document. It also initialises a Nektar++ session
 * including setting up communication for parallel execution and where
 * necessary partitioning the supplied mesh for running across multiple
 * processes.
 *
 * A session should be initialised at the beginning of a user's
 * application by passing the command-line arguments. This not only
 * allows the SessionReader to extract the name of the XML document to
 * load containing Nektar++ session information, but also supplies the
 * MPI arguments necessary for setting up parallel communication. The
 * SessionReader should be initialised using the #CreateInstance
 * function:
 * @code
 * LibUtilities::SessionReaderSharedPtr vSession
 *          = LibUtilities::SessionReader::CreateInstance(argc, argv);
 * @endcode
 * The instance \c vSession can now be passed to other key Nektar++
 * components during their construction.
 * @note At the end of the user application, it is important to call the
 * #Finalise routine in order to finalise any MPI communication and
 * correctly free resources.
 *
 * The SessionReader class provides streamlined, validated access to
 * session parameters, solver information and functions defined within a
 * Nektar++ XML document. The available routines and their usage is
 * documented below.
 *
 * In the case of solver information properties, the classes to which
 * these parameters are pertinent may register with the SessionReader
 * class the set of valid values for a given property. Such values may
 * also be associated with an enumeration value for more transparent use
 * of the property values in code.
 */

/**
 * This map of maps stores the list of valid string values for a number
 * of solver information parameters. The top level map connects
 * different parameter names to their list of possible values. The list
 * of possible values is also a map, mapping a valid string to a
 * corresponding enum value.
 *
 * This list is populated through the #RegisterEnumValue static member
 * function which is called statically from various classes to register
 * the valid values for solver info parameters associated with them. The
 * map is therefore fully populated before the SessionReader class is
 * instantiated and a file is read in and parsed.
 */
EnumMapList &SessionReader::GetSolverInfoEnums()
{
    static EnumMapList solverInfoEnums;
    return solverInfoEnums;
}

/**
 * List of default values for solver information parameters to be used
 * in the case of them not being provided.
 *
 * This list is populated through the #RegisterDefaultSolverInfo static
 * member variable which is called statically from various classes to
 * register the default value for a given parameter.
 */
SolverInfoMap &SessionReader::GetSolverInfoDefaults()
{
    static SolverInfoMap solverInfoMap;
    return solverInfoMap;
}

/**
 * List of values for GlobalSysSoln parameters to be used to override
 * details given in SolverInfo
 *
 * This list is populated by ReadGlobalSysSolnInfo if the
 * GLOBALSYSSOLNINFO section is defined in the input file.
 * This List allows for details to define for the Global Sys
 * solver for each variable.
 */
GloSysSolnInfoList &SessionReader::GetGloSysSolnList()
{
    static GloSysSolnInfoList gloSysSolnInfoList;
    return gloSysSolnInfoList;
}

/**
 * Lists the possible command-line argument which can be specified for
 * this executable.
 *
 * This list is populated through the #RegisterCmdLineArgument static
 * member function which is called statically from various classes to
 * register command-line arguments they need.
 */
CmdLineArgMap &SessionReader::GetCmdLineArgMap()
{
    static CmdLineArgMap cmdLineArguments;
    return cmdLineArguments;
}

/**
 * This constructor parses the command-line arguments given to the user
 * application to set up any MPI communication, read supplied XML
 * session files, and partition meshes where necessary.
 *
 * @param   argc        Number of command-line arguments
 * @param   argv        Array of command-line arguments
 */
SessionReader::SessionReader(int argc, char *argv[])
{
    m_xmlDoc    = nullptr;
    m_filenames = ParseCommandLineArguments(argc, argv);

    ASSERTL0(m_filenames.size() > 0, "No session file(s) given.");

    m_sessionName = ParseSessionName(m_filenames);

    // Create communicator
    CreateComm(argc, argv);

    TestSharedFilesystem();

    // Split up the communicator
    PartitionComm();

    // If running in parallel change the default global sys solution
    // type.
    if (m_comm->GetSpaceComm()->GetSize() > 1)
    {
        GetSolverInfoDefaults()["GLOBALSYSSOLN"] = "IterativeStaticCond";
    }

    m_interpreter = MemoryManager<Interpreter>::AllocateSharedPtr();
    m_interpreter->SetRandomSeed((m_comm->GetSpaceComm()->GetRank() + 1) *
                                 (unsigned int)time(nullptr));
}

/**
 *
 */
SessionReader::SessionReader(int argc, char *argv[],
                             const std::vector<std::string> &pFilenames,
                             const CommSharedPtr &pComm, const int &timelevel)
{
    ASSERTL0(pFilenames.size() > 0, "No filenames specified.");

    ParseCommandLineArguments(argc, argv);
    m_xmlDoc    = nullptr;
    m_filenames = pFilenames;

    m_sessionName = ParseSessionName(m_filenames);

    // Create communicator
    if (!pComm.get())
    {
        CreateComm(argc, argv);
    }
    else
    {
        m_comm = pComm;
    }

    TestSharedFilesystem();

    // Split up the communicator
    PartitionComm();

    // If running in parallel change the default global sys solution
    // type.
    if (m_comm->GetSpaceComm()->GetSize() > 1)
    {
        GetSolverInfoDefaults()["GLOBALSYSSOLN"] = "IterativeStaticCond";
    }

    m_interpreter = MemoryManager<Interpreter>::AllocateSharedPtr();
    m_interpreter->SetRandomSeed((m_comm->GetSpaceComm()->GetRank() + 1) *
                                 (unsigned int)time(nullptr));

    // Set time level (Parallel-in-Time)
    m_timeLevel = timelevel;
}

/**
 *
 */
SessionReader::~SessionReader()
{
    if (m_xmlDoc)
    {
        delete m_xmlDoc;
    }
}

/**
 * Performs the main initialisation of the object. The XML file provided
 * on the command-line is loaded and any mesh partitioning is done. The
 * resulting process-specific XML file (containing the process's
 * geometry partition) is then reloaded and parsed.
 */
void SessionReader::InitSession(const std::vector<std::string> &filenames)
{
    // Re-load filenames for session if required.
    if (filenames.size() > 0)
    {
        m_filenames = filenames;
    }

    // check specified  opt file
    std::string optfile;
    int exists;

    if (DefinesCmdLineArgument("use-opt-file"))
    {
        optfile =
            m_cmdLineOptions.find("use-opt-file")->second.as<std::string>();
        exists = fs::exists(optfile.c_str());
        ASSERTL0(exists, "A valid .opt file was not specified "
                         "with the --use-opt-file command line option");

        m_filenames.push_back(optfile);

        // put opt file at beginning
        std::rotate(m_filenames.rbegin(), m_filenames.rbegin() + 1,
                    m_filenames.rend());
    }
    else // check for write-opt-file
    {
        // check for opt file
        optfile = m_sessionName.substr(0, m_sessionName.find("_xml/")) + ".opt";
        exists  = fs::exists(optfile.c_str());

        // For Paralell-in-Time
        if (exists && m_comm->IsParallelInTime())
        {
            TiXmlDocument doc;
            doc.LoadFile(optfile);
            TiXmlElement *xmlTag = doc.FirstChildElement("NEKTAR")
                                       ->FirstChildElement("COLLECTIONS")
                                       ->FirstChildElement("TIMELEVEL");
            if (xmlTag)
            {
                // if there is a TIMELEVEL tag, then turn existing flag to false
                // and check if the required time level is specified in the
                // optfile.
                exists = false;
                while (xmlTag)
                {
                    if (m_timeLevel == stoi(xmlTag->Attribute("VALUE")))
                    {
                        exists = true;
                        break;
                    }
                    xmlTag = xmlTag->NextSiblingElement();
                }
            }
        }

        if (exists)
        {
            m_filenames.push_back(optfile);
            // rotate order so opt file can be overwritten by
            // direct choice in xml file
            std::rotate(m_filenames.rbegin(), m_filenames.rbegin() + 1,
                        m_filenames.rend());
        }
        else
        {
            m_updateOptFile = true;
        }
    }

    // Merge document if required.
    if (m_xmlDoc)
    {
        delete m_xmlDoc;
    }

    m_xmlDoc = MergeDoc(m_filenames);

    // Parse the XML data in #m_xmlDoc
    ParseDocument();

    // Override SOLVERINFO and parameters with any specified on the
    // command line.
    CmdLineOverride();

    // Verify SOLVERINFO values
    VerifySolverInfo();

    // Disable backups if NEKTAR_DISABLE_BACKUPS is set.
    if (std::getenv("NEKTAR_DISABLE_BACKUPS") != nullptr)
    {
        m_backups = false;
    }

    // In verbose mode, print out parameters and solver info sections
    if (m_verbose && m_comm)
    {
        if (m_comm->TreatAsRankZero() && m_parameters.size() > 0)
        {
            cout << "Parameters:" << endl;
            for (auto &x : m_parameters)
            {
                cout << "\t" << x.first << " = " << x.second << endl;
            }
            cout << endl;
        }

        if (m_comm->TreatAsRankZero() && m_solverInfo.size() > 0)
        {
            cout << "Solver Info:" << endl;
            for (auto &x : m_solverInfo)
            {
                cout << "\t" << x.first << " = " << x.second << endl;
            }
            cout << endl;
        }
    }
}

/**
 *
 */
void SessionReader::TestSharedFilesystem()
{
    m_sharedFilesystem = false;

    if (m_comm->GetSize() > 1)
    {
        if (m_comm->GetRank() == 0)
        {
            std::ofstream testfile("shared-fs-testfile");
            testfile << "" << std::endl;
            ASSERTL1(!testfile.fail(), "Test file creation failed");
            testfile.close();
        }
        m_comm->Block();

        int exists = fs::exists("shared-fs-testfile");
        m_comm->AllReduce(exists, LibUtilities::ReduceSum);

        m_sharedFilesystem = (exists == m_comm->GetSize());

        if ((m_sharedFilesystem && m_comm->GetRank() == 0) ||
            !m_sharedFilesystem)
        {
            std::remove("shared-fs-testfile");
        }
    }
    else
    {
        m_sharedFilesystem = false;
    }

    if (m_verbose && m_comm->GetRank() == 0 && m_sharedFilesystem)
    {
        cout << "Shared filesystem detected" << endl;
    }
}

/**
 * @brief Parses the command-line arguments for known options and
 * filenames.
 */
std::vector<std::string> SessionReader::ParseCommandLineArguments(int argc,
                                                                  char *argv[])
{
    // List the publically visible options (listed using --help).
    po::options_description desc("Allowed options");
    po::options_description dep("Deprecated options");

    // clang-format off
    desc.add_options()
        ("force-output,f","disables backups files and forces output to be "
                          "written without any checks")
        ("help,h",       "print this help message")
        ("solverinfo,I", po::value<vector<std::string>>(),
                         "override a SOLVERINFO property")
        ("parameter,P",  po::value<vector<std::string>>(),
                         "override a parameter")
        ("verbose,v",    "be verbose")
        ("version,V",    "print version information")
        ("no-exp-opt",   "Do not use expansion optimisation for collections")
        ("npx",          po::value<int>(),
                         "number of procs in X-dir")
        ("npy",          po::value<int>(),
                         "number of procs in Y-dir")
        ("npz",          po::value<int>(),
                         "number of procs in Z-dir")
        ("nsz",          po::value<int>(),
                         "number of slices in Z-dir")
        ("npt",          po::value<int>(),
                         "number of procs in T-dir (parareal)")
        ("part-only",    po::value<int>(),
                         "only partition mesh into N partitions.")
        ("part-only-overlapping",    po::value<int>(),
                         "only partition mesh into N overlapping partitions.")
        ("part-info",    "output partition information")
        ("write-opt-file","write an optimisation file")
        ("use-opt-file", po::value<std::string>(),
                         "use an optimisation file");
    // clang-format on

#ifdef NEKTAR_USE_CWIPI
    desc.add_options()("cwipi", po::value<std::string>(), "set CWIPI name");
#endif

    for (auto &cmdIt : GetCmdLineArgMap())
    {
        std::string names = cmdIt.first;
        if (cmdIt.second.shortName != "")
        {
            names += "," + cmdIt.second.shortName;
        }
        if (cmdIt.second.isFlag)
        {
            desc.add_options()(names.c_str(), cmdIt.second.description.c_str());
        }
        else
        {
            desc.add_options()(names.c_str(), po::value<std::string>(),
                               cmdIt.second.description.c_str());
        }
    }

    // Deprecated options: introduced in 5.4.0 to homogenise command-line
    // options to use '-' instead of camelCase or no spaces.
    std::map<std::string, std::string> deprecated = {
        {"forceoutput", "force-output"},
        {"writeoptfile", "write-opt-file"},
        {"useoptfile", "use-opt-file"}};

    for (auto &d : deprecated)
    {
        std::string description = "Deprecated: use --" + d.second;
        dep.add_options()(d.first.c_str(), description.c_str());
    }

    // List hidden options (e.g. session file arguments are not actually
    // specified using the input-file option by the user).
    po::options_description hidden("Hidden options");

    hidden.add_options()("input-file", po::value<vector<string>>(),
                         "input filename");

    // Combine all options for the parser
    po::options_description all("All options");
    all.add(desc).add(dep).add(hidden);

    // Session file is a positional option
    po::positional_options_description p;
    p.add("input-file", -1);

    // Parse the command-line options
    po::parsed_options parsed = po::command_line_parser(argc, argv)
                                    .options(all)
                                    .positional(p)
                                    .allow_unregistered()
                                    .run();

    // Extract known options to map and update
    po::store(parsed, m_cmdLineOptions);
    po::notify(m_cmdLineOptions);

    // Help message
    if (m_cmdLineOptions.count("help"))
    {
        cout << desc;
        exit(0);
    }

    // Version information
    if (m_cmdLineOptions.count("version"))
    {
        cout << "Nektar++ version " << NEKTAR_VERSION;

        if (NekConstants::kGitSha1 != "GITDIR-NOTFOUND")
        {
            string sha1(NekConstants::kGitSha1);
            string branch(NekConstants::kGitBranch);
            boost::replace_all(branch, "refs/heads/", "");

            cout << " (git changeset " << sha1.substr(0, 8) << ", ";

            if (branch == "")
            {
                cout << "detached head";
            }
            else
            {
                cout << "head " << branch;
            }

            cout << ")";
        }

        cout << endl;
        exit(0);
    }

    // Deal with deprecated options.
    for (auto &d : deprecated)
    {
        if (m_cmdLineOptions.count(d.first))
        {
            std::cerr << "Warning: --" << d.first << " deprecated: use --"
                      << d.second << std::endl;
            m_cmdLineOptions.emplace(
                d.second, po::variable_value(m_cmdLineOptions[d.first]));
        }
    }

    // Enable verbose mode
    if (m_cmdLineOptions.count("verbose"))
    {
        m_verbose = true;
    }
    else
    {
        m_verbose = false;
    }

    // Disable backups
    if (m_cmdLineOptions.count("force-output"))
    {
        m_backups = false;
    }
    else
    {
        m_backups = true;
    }

    // Enable update optimisation file
    if (m_cmdLineOptions.count("write-opt-file"))
    {
        m_updateOptFile = true;
    }
    else
    {
        m_updateOptFile = false;
    }

    // Print a warning for unknown options
    for (auto &x : parsed.options)
    {
        if (x.unregistered)
        {
            cout << "Warning: Unknown option: " << x.string_key << endl;
        }
    }

    // Return the vector of filename(s) given as positional options
    if (m_cmdLineOptions.count("input-file"))
    {
        return m_cmdLineOptions["input-file"].as<std::vector<std::string>>();
    }
    else
    {
        return std::vector<std::string>();
    }
}

/**
 *
 */
std::string SessionReader::ParseSessionName(std::vector<std::string> &filenames)
{
    ASSERTL0(!filenames.empty(), "At least one filename expected.");

    std::string retval = "";

    // First input file defines the session name
    std::string fname = filenames[0];

    // If loading a pre-partitioned mesh, remove _xml extension
    if (fname.size() > 4 && fname.substr(fname.size() - 4, 4) == "_xml")
    {
        retval = fname.substr(0, fname.find_last_of("_"));
    }
    // otherwise remove the .xml extension
    else if (fname.size() > 4 && fname.substr(fname.size() - 4, 4) == ".xml")
    {
        retval = fname.substr(0, fname.find_last_of("."));
    }
    // If compressed .xml.gz, remove both extensions
    else if (fname.size() > 7 && fname.substr(fname.size() - 7, 7) == ".xml.gz")
    {
        retval = fname.substr(0, fname.find_last_of("."));
        retval = retval.substr(0, retval.find_last_of("."));
    }

    return retval;
}

/**
 *
 */
TiXmlDocument &SessionReader::GetDocument()
{
    ASSERTL1(m_xmlDoc, "XML Document not defined.");
    return *m_xmlDoc;
}

/**
 * The single parameter specifies a path to the requested element in a
 * similar format to the filesystem path. Given the following XML:
 * @code
 * <NEKTAR>
 *   <CONDITIONS>
 *     <PARAMETERS>
 *     ...
 *     </PARAMETERS>
 *   </CONDITIONS>
 * </NEKTAR>
 * @endcode
 * the PARAMETERS element would be retrieved by requesting the path:
 * @code
 * Nektar/Conditions/Parameters
 * @endcode
 * @note Paths are case-insensitive.
 *
 * @param   pPath       Path to requested element.
 *
 * @return Direct pointer to requested XML Element.
 */
TiXmlElement *SessionReader::GetElement(const string &pPath)
{
    std::string vPath = boost::to_upper_copy(pPath);
    std::vector<std::string> st;
    boost::split(st, vPath, boost::is_any_of("\\/ "));
    ASSERTL0(st.size() > 0, "No path given in XML element request.");

    TiXmlElement *vReturn = m_xmlDoc->FirstChildElement(st[0].c_str());
    ASSERTL0(vReturn,
             std::string("Cannot find element '") + st[0] + std::string("'."));
    for (int i = 1; i < st.size(); ++i)
    {
        vReturn = vReturn->FirstChildElement(st[i].c_str());
        ASSERTL0(vReturn, std::string("Cannot find element '") + st[i] +
                              std::string("'."));
    }
    return vReturn;
}

/**
 *
 */
bool SessionReader::DefinesElement(const std::string &pPath) const
{
    std::string vPath = boost::to_upper_copy(pPath);
    std::vector<std::string> st;
    boost::split(st, vPath, boost::is_any_of("\\/ "));
    ASSERTL0(st.size() > 0, "No path given in XML element request.");

    TiXmlElement *vReturn = m_xmlDoc->FirstChildElement(st[0].c_str());
    ASSERTL0(vReturn,
             std::string("Cannot find element '") + st[0] + std::string("'."));
    for (int i = 1; i < st.size(); ++i)
    {
        vReturn = vReturn->FirstChildElement(st[i].c_str());
        if (!vReturn)
        {
            return false;
        }
    }
    return true;
}

/**
 *
 */
const std::vector<std::string> &SessionReader::GetFilenames() const
{
    return m_filenames;
}

/**
 *
 */
const std::string &SessionReader::GetSessionName() const
{
    return m_sessionName;
}

/**
 *
 */
CommSharedPtr SessionReader::GetComm()
{
    return m_comm;
}

bool SessionReader::GetSharedFilesystem()
{
    return m_sharedFilesystem;
}

/**
 * This routine finalises any parallel communication.
 *
 * @note This routine should be called at the very end of a users
 * application.
 */
void SessionReader::Finalise()
{
    m_comm->Finalise();
}

/**
 *
 */
bool SessionReader::DefinesParameter(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    return m_parameters.find(vName) != m_parameters.end();
}

/**
 * If the parameter is not defined, termination occurs. Therefore, the
 * parameters existence should be tested for using #DefinesParameter
 * before calling this function.
 *
 * @param   pName       The name of a floating-point parameter.
 * @returns The value of the floating-point parameter.
 */
const NekDouble &SessionReader::GetParameter(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);

    ASSERTL0(paramIter != m_parameters.end(),
             "Unable to find requested parameter: " + pName);

    return paramIter->second;
}

/**
 * Getter for the session's parameters
 * @returns A reference to the parameter map
 */
const ParameterMap &SessionReader::GetParameters()
{
    return m_parameters;
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, int &pVar) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    ASSERTL0(paramIter != m_parameters.end(),
             "Required parameter '" + pName + "' not specified in session.");
    NekDouble param = round(paramIter->second);
    pVar            = checked_cast<int>(param);
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, int &pVar,
                                  const int &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    if (paramIter != m_parameters.end())
    {
        NekDouble param = round(paramIter->second);
        pVar            = checked_cast<int>(param);
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName,
                                  unsigned int &pVar) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    ASSERTL0(paramIter != m_parameters.end(),
             "Required parameter '" + pName + "' not specified in session.");
    NekDouble param = round(paramIter->second);
    pVar            = checked_cast<unsigned int>(param);
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, unsigned int &pVar,
                                  const unsigned int &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    if (paramIter != m_parameters.end())
    {
        NekDouble param = round(paramIter->second);
        pVar            = checked_cast<unsigned int>(param);
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, size_t &pVar) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    ASSERTL0(paramIter != m_parameters.end(),
             "Required parameter '" + pName + "' not specified in session.");
    NekDouble param = round(paramIter->second);
    pVar            = checked_cast<int>(param);
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, size_t &pVar,
                                  const size_t &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    if (paramIter != m_parameters.end())
    {
        NekDouble param = round(paramIter->second);
        pVar            = checked_cast<int>(param);
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName,
                                  NekDouble &pVar) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    ASSERTL0(paramIter != m_parameters.end(),
             "Required parameter '" + pName + "' not specified in session.");
    pVar = paramIter->second;
}

/**
 *
 */
void SessionReader::LoadParameter(const std::string &pName, NekDouble &pVar,
                                  const NekDouble &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto paramIter    = m_parameters.find(vName);
    if (paramIter != m_parameters.end())
    {
        pVar = paramIter->second;
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
void SessionReader::SetParameter(const std::string &pName, int &pVar)
{
    std::string vName   = boost::to_upper_copy(pName);
    m_parameters[vName] = pVar;
}

/**
 *
 */
void SessionReader::SetParameter(const std::string &pName, unsigned int &pVar)
{
    std::string vName   = boost::to_upper_copy(pName);
    m_parameters[vName] = pVar;
}

/**
 *
 */
void SessionReader::SetParameter(const std::string &pName, size_t &pVar)
{
    std::string vName   = boost::to_upper_copy(pName);
    m_parameters[vName] = pVar;
}

/**
 *
 */
void SessionReader::SetParameter(const std::string &pName, NekDouble &pVar)
{
    std::string vName   = boost::to_upper_copy(pName);
    m_parameters[vName] = pVar;
}

/**
 *
 */
bool SessionReader::DefinesSolverInfo(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto infoIter     = m_solverInfo.find(vName);
    return (infoIter != m_solverInfo.end());
}

/**
 *
 */
const std::string &SessionReader::GetSolverInfo(
    const std::string &pProperty) const
{
    std::string vProperty = boost::to_upper_copy(pProperty);
    auto iter             = m_solverInfo.find(vProperty);

    ASSERTL1(iter != m_solverInfo.end(),
             "Unable to find requested property: " + pProperty);

    return iter->second;
}

/**
 *
 */
void SessionReader::SetSolverInfo(const std::string &pProperty,
                                  const std::string &pValue)
{
    std::string vProperty = boost::to_upper_copy(pProperty);
    auto iter             = m_solverInfo.find(vProperty);

    ASSERTL1(iter != m_solverInfo.end(),
             "Unable to find requested property: " + pProperty);

    iter->second = pValue;
}

/**
 *
 */
void SessionReader::LoadSolverInfo(const std::string &pName, std::string &pVar,
                                   const std::string &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto infoIter     = m_solverInfo.find(vName);
    if (infoIter != m_solverInfo.end())
    {
        pVar = infoIter->second;
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
void SessionReader::MatchSolverInfo(const std::string &pName,
                                    const std::string &pTrueVal, bool &pVar,
                                    const bool &pDefault) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto infoIter     = m_solverInfo.find(vName);
    if (infoIter != m_solverInfo.end())
    {
        pVar = boost::iequals(infoIter->second, pTrueVal);
    }
    else
    {
        pVar = pDefault;
    }
}

/**
 *
 */
bool SessionReader::MatchSolverInfo(const std::string &pName,
                                    const std::string &pTrueVal) const
{
    if (DefinesSolverInfo(pName))
    {
        std::string vName = boost::to_upper_copy(pName);
        auto iter         = m_solverInfo.find(vName);
        if (iter != m_solverInfo.end())
        {
            return boost::iequals(iter->second, pTrueVal);
        }
    }
    return false;
}

/**
 *
 */
bool SessionReader::DefinesGlobalSysSolnInfo(const std::string &pVariable,
                                             const std::string &pProperty) const
{
    auto iter = GetGloSysSolnList().find(pVariable);
    if (iter == GetGloSysSolnList().end())
    {
        return false;
    }

    std::string vProperty = boost::to_upper_copy(pProperty);

    auto iter1 = iter->second.find(vProperty);
    if (iter1 == iter->second.end())
    {
        return false;
    }

    return true;
}

/**
 *
 */
const std::string &SessionReader::GetGlobalSysSolnInfo(
    const std::string &pVariable, const std::string &pProperty) const
{
    auto iter = GetGloSysSolnList().find(pVariable);
    ASSERTL0(iter != GetGloSysSolnList().end(),
             "Failed to find variable in GlobalSysSolnInfoList");

    std::string vProperty = boost::to_upper_copy(pProperty);
    auto iter1            = iter->second.find(vProperty);

    ASSERTL0(iter1 != iter->second.end(),
             "Failed to find property: " + vProperty +
                 " in GlobalSysSolnInfoList");

    return iter1->second;
}

/**
 * @brief Returns true if the TIMEINTEGRATIONSCHEME section is defined
 * in the session file.
 */
bool SessionReader::DefinesTimeIntScheme() const
{
    return m_timeIntScheme.method != "";
}

/**
 * @brief Returns the time integration scheme structure #m_timeIntScheme
 * from the session file.
 */
const TimeIntScheme &SessionReader::GetTimeIntScheme() const
{
    return m_timeIntScheme;
}

/**
 *
 */
std::string SessionReader::GetGeometryType() const
{
    TiXmlElement *xmlGeom =
        m_xmlDoc->FirstChildElement("NEKTAR")->FirstChildElement("GEOMETRY");
    ASSERTL1(xmlGeom, "Failed to find a GEOMETRY section in m_xmlDoc");

    TiXmlAttribute *attr = xmlGeom->FirstAttribute();
    while (attr)
    {
        std::string attrName(attr->Name());
        if (attrName == "HDF5FILE")
        {
            // there is a file pointer, therefore is HDF5
            return "HDF5";
        }
        // Get the next attribute.
        attr = attr->Next();
    }

    // Check the VERTEX block. If this is compressed, assume the file is
    // compressed, otherwise assume uncompressed.
    TiXmlElement *element = xmlGeom->FirstChildElement("VERTEX");
    string IsCompressed;
    element->QueryStringAttribute("COMPRESSED", &IsCompressed);

    if (IsCompressed.size() > 0)
    {
        return "XmlCompressed";
    }

    // no file pointer or compressed, just standard xml
    return "Xml";
}

/**
 *
 */
const std::string &SessionReader::GetVariable(const unsigned int &idx) const
{
    ASSERTL0(idx < m_variables.size(), "Variable index out of range.");
    return m_variables[idx];
}

/**
 *
 */
void SessionReader::SetVariable(const unsigned int &idx, std::string newname)
{
    ASSERTL0(idx < m_variables.size(), "Variable index out of range.");
    m_variables[idx] = newname;
}

/**
 *
 */
std::vector<std::string> SessionReader::GetVariables() const
{
    return m_variables;
}

/**
 *
 */
bool SessionReader::GetBackups() const
{
    return m_backups;
}

/**
 *
 */
bool SessionReader::DefinesFunction(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    return m_functions.find(vName) != m_functions.end();
}

/**
 *
 */
bool SessionReader::DefinesFunction(const std::string &pName,
                                    const std::string &pVariable,
                                    const int pDomain) const
{
    std::string vName = boost::to_upper_copy(pName);

    // Check function exists
    auto it1 = m_functions.find(vName);
    if (it1 != m_functions.end())
    {
        pair<std::string, int> key(pVariable, pDomain);
        pair<std::string, int> defkey("*", pDomain);
        bool varExists = it1->second.find(key) != it1->second.end() ||
                         it1->second.find(defkey) != it1->second.end();
        return varExists;
    }
    return false;
}

/**
 *
 */
EquationSharedPtr SessionReader::GetFunction(const std::string &pName,
                                             const std::string &pVariable,
                                             const int pDomain) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto it1          = m_functions.find(vName);

    ASSERTL0(it1 != m_functions.end(),
             std::string("No such function '") + pName +
                 std::string("' has been defined in the session file."));

    // Check for specific and wildcard definitions
    pair<std::string, int> key(pVariable, pDomain);
    pair<std::string, int> defkey("*", pDomain);

    auto it2      = it1->second.find(key);
    auto it3      = it1->second.find(defkey);
    bool specific = it2 != it1->second.end();
    bool wildcard = it3 != it1->second.end();

    // Check function is defined somewhere
    ASSERTL0(specific || wildcard, "No such variable " + pVariable +
                                       " in domain " + std::to_string(pDomain) +
                                       " defined for function " + pName +
                                       " in session file.");

    // If not specific, must be wildcard
    if (!specific)
    {
        it2 = it3;
    }

    ASSERTL0((it2->second.m_type == eFunctionTypeExpression),
             std::string("Function is defined by a file."));
    return it2->second.m_expression;
}

/**
 *
 */
EquationSharedPtr SessionReader::GetFunction(const std::string &pName,
                                             const unsigned int &pVar,
                                             const int pDomain) const
{
    ASSERTL0(pVar < m_variables.size(), "Variable index out of range.");
    return GetFunction(pName, m_variables[pVar], pDomain);
}

/**
 *
 */
enum FunctionType SessionReader::GetFunctionType(const std::string &pName,
                                                 const std::string &pVariable,
                                                 const int pDomain) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto it1          = m_functions.find(vName);

    ASSERTL0(it1 != m_functions.end(),
             std::string("Function '") + pName + std::string("' not found."));

    // Check for specific and wildcard definitions
    pair<std::string, int> key(pVariable, pDomain);
    pair<std::string, int> defkey("*", pDomain);

    auto it2      = it1->second.find(key);
    auto it3      = it1->second.find(defkey);
    bool specific = it2 != it1->second.end();
    bool wildcard = it3 != it1->second.end();

    // Check function is defined somewhere
    ASSERTL0(specific || wildcard, "No such variable " + pVariable +
                                       " in domain " + std::to_string(pDomain) +
                                       " defined for function " + pName +
                                       " in session file.");

    // If not specific, must be wildcard
    if (!specific)
    {
        it2 = it3;
    }

    return it2->second.m_type;
}

/**
 *
 */
enum FunctionType SessionReader::GetFunctionType(const std::string &pName,
                                                 const unsigned int &pVar,
                                                 const int pDomain) const
{
    ASSERTL0(pVar < m_variables.size(), "Variable index out of range.");
    return GetFunctionType(pName, m_variables[pVar], pDomain);
}

/**
 *
 */
std::string SessionReader::GetFunctionFilename(const std::string &pName,
                                               const std::string &pVariable,
                                               const int pDomain) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto it1          = m_functions.find(vName);

    ASSERTL0(it1 != m_functions.end(),
             std::string("Function '") + pName + std::string("' not found."));

    // Check for specific and wildcard definitions
    pair<std::string, int> key(pVariable, pDomain);
    pair<std::string, int> defkey("*", pDomain);

    auto it2      = it1->second.find(key);
    auto it3      = it1->second.find(defkey);
    bool specific = it2 != it1->second.end();
    bool wildcard = it3 != it1->second.end();

    // Check function is defined somewhere
    ASSERTL0(specific || wildcard, "No such variable " + pVariable +
                                       " in domain " + std::to_string(pDomain) +
                                       " defined for function " + pName +
                                       " in session file.");

    // If not specific, must be wildcard
    if (!specific)
    {
        it2 = it3;
    }

    return it2->second.m_filename;
}

/**
 *
 */
std::string SessionReader::GetFunctionFilename(const std::string &pName,
                                               const unsigned int &pVar,
                                               const int pDomain) const
{
    ASSERTL0(pVar < m_variables.size(), "Variable index out of range.");
    return GetFunctionFilename(pName, m_variables[pVar], pDomain);
}

/**
 *
 */
std::string SessionReader::GetFunctionFilenameVariable(
    const std::string &pName, const std::string &pVariable,
    const int pDomain) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto it1          = m_functions.find(vName);

    ASSERTL0(it1 != m_functions.end(),
             std::string("Function '") + pName + std::string("' not found."));

    // Check for specific and wildcard definitions
    pair<std::string, int> key(pVariable, pDomain);
    pair<std::string, int> defkey("*", pDomain);

    auto it2      = it1->second.find(key);
    auto it3      = it1->second.find(defkey);
    bool specific = it2 != it1->second.end();
    bool wildcard = it3 != it1->second.end();

    // Check function is defined somewhere
    ASSERTL0(specific || wildcard, "No such variable " + pVariable +
                                       " in domain " + std::to_string(pDomain) +
                                       " defined for function " + pName +
                                       " in session file.");

    // If not specific, must be wildcard
    if (!specific)
    {
        it2 = it3;
    }

    return it2->second.m_fileVariable;
}

/**
 *
 */
bool SessionReader::DefinesTag(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    return m_tags.find(vName) != m_tags.end();
}

/**
 *
 */
void SessionReader::SetTag(const std::string &pName, const std::string &pValue)
{
    std::string vName = boost::to_upper_copy(pName);
    m_tags[vName]     = pValue;
}

/**
 *
 */
const std::string &SessionReader::GetTag(const std::string &pName) const
{
    std::string vName = boost::to_upper_copy(pName);
    auto vTagIterator = m_tags.find(vName);
    ASSERTL0(vTagIterator != m_tags.end(), "Requested tag does not exist.");
    return vTagIterator->second;
}

/**
 *
 */
const FilterMap &SessionReader::GetFilters() const
{
    return m_filters;
}

/**
 *
 */
bool SessionReader::DefinesCmdLineArgument(const std::string &pName) const
{
    return (m_cmdLineOptions.find(pName) != m_cmdLineOptions.end());
}

/**
 *
 */
void SessionReader::GetXMLElementTimeLevel(TiXmlElement *&Element,
                                           const size_t timeLevel,
                                           const bool enableCheck)
{
    if (Element && Element->FirstChildElement("TIMELEVEL"))
    {
        Element = Element->FirstChildElement("TIMELEVEL");
        std::string timeLevelStr;
        while (Element)
        {
            std::stringstream tagcontent;
            tagcontent << *Element;
            ASSERTL0(Element->Attribute("VALUE"),
                     "Missing LEVEL attribute in solver info "
                     "XML element: \n\t'" +
                         tagcontent.str() + "'");
            timeLevelStr = Element->Attribute("VALUE");
            ASSERTL0(!timeLevelStr.empty(),
                     "LEVEL attribute must be non-empty in XML "
                     "element: \n\t'" +
                         tagcontent.str() + "'");
            if (stoi(timeLevelStr) == timeLevel)
            {
                break;
            }
            Element = Element->NextSiblingElement("TIMELEVEL");
        }
        if (enableCheck)
        {
            ASSERTL0(stoi(timeLevelStr) == timeLevel,
                     "TIMELEVEL value " + std::to_string(timeLevel) +
                         " not found in solver info "
                         "XML element: \n\t'");
        }
    }
}

/**
 *
 */
void SessionReader::LoadDoc(const std::string &pFilename,
                            TiXmlDocument *pDoc) const
{
    if (pFilename.size() > 3 &&
        pFilename.substr(pFilename.size() - 3, 3) == ".gz")
    {
        ifstream file(pFilename.c_str(), ios_base::in | ios_base::binary);
        ASSERTL0(file.good(), "Unable to open file: " + pFilename);
        stringstream ss;
        io::filtering_streambuf<io::input> in;
        in.push(io::gzip_decompressor());
        in.push(file);
        try
        {
            io::copy(in, ss);
            ss >> (*pDoc);
        }
        catch (io::gzip_error &)
        {
            NEKERROR(ErrorUtil::efatal,
                     "Error: File '" + pFilename + "' is corrupt.");
        }
    }
    else if (pFilename.size() > 4 &&
             pFilename.substr(pFilename.size() - 4, 4) == "_xml")
    {
        fs::path pdirname(pFilename);
        boost::format pad("P%1$07d.xml");
        pad % m_comm->GetSpaceComm()->GetRank();
        fs::path pRankFilename(pad.str());
        fs::path fullpath = pdirname / pRankFilename;

        ifstream file(PortablePath(fullpath).c_str());
        ASSERTL0(file.good(), "Unable to open file: " + fullpath.string());
        file >> (*pDoc);
    }
    else
    {
        ifstream file(pFilename.c_str());
        ASSERTL0(file.good(), "Unable to open file: " + pFilename);
        file >> (*pDoc);
    }
}

/**
 *
 */
TiXmlDocument *SessionReader::MergeDoc(
    const std::vector<std::string> &pFilenames) const
{
    ASSERTL0(pFilenames.size() > 0, "No filenames for merging.");

    // Read the first document
    TiXmlDocument *vMainDoc = new TiXmlDocument;
    LoadDoc(pFilenames[0], vMainDoc);

    TiXmlHandle vMainHandle(vMainDoc);
    TiXmlElement *vMainNektar =
        GetChildElementOrThrow(pFilenames[0], "NEKTAR", vMainHandle);

    // Read all subsequent XML documents.
    // For each element within the NEKTAR tag, use it to replace the
    // version already present in the loaded XML data.
    for (int i = 1; i < pFilenames.size(); ++i)
    {
        if ((pFilenames[i].compare(pFilenames[i].size() - 3, 3, "xml") == 0) ||
            (pFilenames[i].compare(pFilenames[i].size() - 6, 6, "xml.gz") ==
             0) ||
            (pFilenames[i].compare(pFilenames[i].size() - 3, 3, "opt") == 0))
        {
            TiXmlDocument *vTempDoc = new TiXmlDocument;
            LoadDoc(pFilenames[i], vTempDoc);

            TiXmlHandle docHandle(vTempDoc);
            TiXmlElement *vTempNektar =
                GetChildElementOrThrow(pFilenames[i], "NEKTAR", docHandle);
            TiXmlElement *p = vTempNektar->FirstChildElement();

            while (p)
            {
                TiXmlElement *vMainEntry =
                    vMainNektar->FirstChildElement(p->Value());

                // First check if the new item is in fact blank
                // replace if it is a COLLECTIONS section however.
                if (!p->FirstChild() && vMainEntry &&
                    !boost::iequals(p->Value(), "COLLECTIONS"))
                {
                    std::string warningmsg =
                        "File " + pFilenames[i] + " contains " +
                        "an empty XML element " + std::string(p->Value()) +
                        " which will be ignored.";
                    NEKERROR(ErrorUtil::ewarning, warningmsg.c_str());
                }
                else
                {
                    if (vMainEntry)
                    {
                        vMainNektar->RemoveChild(vMainEntry);
                    }
                    TiXmlElement *q = new TiXmlElement(*p);
                    vMainNektar->LinkEndChild(q);
                }
                p = p->NextSiblingElement();
            }
            delete vTempDoc;
        }
    }
    return vMainDoc;
}

/**
 *
 */
void SessionReader::ParseDocument()
{
    // Check we actually have a document loaded.
    ASSERTL0(m_xmlDoc, "No XML document loaded.");

    TiXmlHandle docHandle(m_xmlDoc);
    TiXmlElement *e;

    // Look for all data in CONDITIONS block.
    e = docHandle.FirstChildElement("NEKTAR")
            .FirstChildElement("CONDITIONS")
            .Element();

    // Read the various sections of the CONDITIONS block
    ReadParameters(e);
    ReadSolverInfo(e);
    ReadGlobalSysSolnInfo(e);
    ReadTimeIntScheme(e);
    ReadVariables(e);
    ReadFunctions(e);

    // Look for all data in FILTERS block.
    e = docHandle.FirstChildElement("NEKTAR")
            .FirstChildElement("FILTERS")
            .Element();

    // Read the various sections of the FILTERS block
    ReadFilters(e);
}

/**
 *
 */
void SessionReader::CreateComm(int &argc, char *argv[])
{
    if (argc == 0)
    {
        m_comm = GetCommFactory().CreateInstance("Serial", 0, 0);
    }
    else
    {
        string vCommModule("Serial");
        if (GetCommFactory().ModuleExists("ParallelMPI"))
        {
            vCommModule = "ParallelMPI";
        }
        if (m_cmdLineOptions.count("cwipi") &&
            GetCommFactory().ModuleExists("CWIPI"))
        {
            vCommModule = "CWIPI";
        }

        m_comm = GetCommFactory().CreateInstance(vCommModule, argc, argv);
    }
}

/**
 * Splits the processes into a cartesian grid and creates communicators
 * for each row and column of the grid.
 */
void SessionReader::PartitionComm()
{
    if (m_comm->GetSize() > 1)
    {
        int nProcZ  = 1;
        int nProcY  = 1;
        int nProcX  = 1;
        int nStripZ = 1;
        int nTime   = 0;
        if (DefinesCmdLineArgument("npx"))
        {
            nProcX = GetCmdLineArgument<int>("npx");
        }
        if (DefinesCmdLineArgument("npy"))
        {
            nProcY = GetCmdLineArgument<int>("npy");
        }
        if (DefinesCmdLineArgument("npz"))
        {
            nProcZ = GetCmdLineArgument<int>("npz");
        }
        if (DefinesCmdLineArgument("nsz"))
        {
            nStripZ = GetCmdLineArgument<int>("nsz");
        }
        if (DefinesCmdLineArgument("npt"))
        {
            nTime = GetCmdLineArgument<int>("npt");
        }

        if (nTime)
        {
            ASSERTL0(m_comm->GetSize() % nTime == 0,
                     "Cannot exactly partition time using npt value.");
            ASSERTL0((m_comm->GetSize() / nTime) % (nProcZ * nProcY * nProcX) ==
                         0,
                     "Cannot exactly partition using PROC_Z value.");
            ASSERTL0(nProcZ % nProcY == 0,
                     "Cannot exactly partition using PROC_Y value.");
            ASSERTL0(nProcY % nProcX == 0,
                     "Cannot exactly partition using PROC_X value.");
        }
        else
        {
            ASSERTL0(m_comm->GetSize() % (nProcZ * nProcY * nProcX) == 0,
                     "Cannot exactly partition using PROC_Z value.");
            ASSERTL0(nProcZ % nProcY == 0,
                     "Cannot exactly partition using PROC_Y value.");
            ASSERTL0(nProcY % nProcX == 0,
                     "Cannot exactly partition using PROC_X value.");
        }

        // Number of processes associated with the spectral method
        int nProcSm = nProcZ * nProcY * nProcX;

        // Number of processes associated with the spectral element
        // method.
        int nProcSem = nTime ? m_comm->GetSize() / nTime / nProcSm
                             : m_comm->GetSize() / nProcSm;

        m_comm->SplitComm(nProcSm, nProcSem, nTime);
        m_comm->GetColumnComm()->SplitComm(nProcZ / nStripZ, nStripZ);
        m_comm->GetColumnComm()->GetColumnComm()->SplitComm((nProcY * nProcX),
                                                            nProcZ / nStripZ);
        m_comm->GetColumnComm()->GetColumnComm()->GetColumnComm()->SplitComm(
            nProcX, nProcY);
    }
}

/**
 *
 */
void SessionReader::ReadParameters(TiXmlElement *conditions)
{
    m_parameters.clear();

    if (!conditions)
    {
        return;
    }

    TiXmlElement *parametersElement =
        conditions->FirstChildElement("PARAMETERS");
    GetXMLElementTimeLevel(parametersElement, m_timeLevel);

    // See if we have parameters defined.  They are optional so we go on
    // if not.
    if (parametersElement)
    {
        TiXmlElement *parameter = parametersElement->FirstChildElement("P");

        ParameterMap caseSensitiveParameters;

        // Multiple nodes will only occur if there is a comment in
        // between definitions.
        while (parameter)
        {
            stringstream tagcontent;
            tagcontent << *parameter;
            TiXmlNode *node = parameter->FirstChild();

            while (node && node->Type() != TiXmlNode::TINYXML_TEXT)
            {
                node = node->NextSibling();
            }

            if (node)
            {
                // Format is "paramName = value"
                std::string line = node->ToText()->Value(), lhs, rhs;

                try
                {
                    ParseEquals(line, lhs, rhs);
                }
                catch (...)
                {
                    NEKERROR(ErrorUtil::efatal,
                             "Syntax error in parameter expression '" + line +
                                 "' in XML element: \n\t'" + tagcontent.str() +
                                 "'");
                }

                // We want the list of parameters to have their RHS
                // evaluated, so we use the expression evaluator to do
                // the dirty work.
                if (!lhs.empty() && !rhs.empty())
                {
                    NekDouble value = 0.0;
                    try
                    {
                        LibUtilities::Equation expession(m_interpreter, rhs);
                        value = expession.Evaluate();
                    }
                    catch (const std::runtime_error &)
                    {
                        NEKERROR(ErrorUtil::efatal,
                                 "Error evaluating parameter expression"
                                 " '" +
                                     rhs + "' in XML element: \n\t'" +
                                     tagcontent.str() + "'");
                    }
                    m_interpreter->SetParameter(lhs, value);
                    caseSensitiveParameters[lhs] = value;
                    boost::to_upper(lhs);
                    m_parameters[lhs] = value;
                }
            }
            parameter = parameter->NextSiblingElement();
        }
    }
}

/**
 *
 */
void SessionReader::ReadSolverInfo(TiXmlElement *conditions)
{
    m_solverInfo.clear();
    m_solverInfo = GetSolverInfoDefaults();

    if (!conditions)
    {
        return;
    }

    TiXmlElement *solverInfoElement =
        conditions->FirstChildElement("SOLVERINFO");
    GetXMLElementTimeLevel(solverInfoElement, m_timeLevel);

    if (solverInfoElement)
    {
        TiXmlElement *solverInfo = solverInfoElement->FirstChildElement("I");

        while (solverInfo)
        {
            std::stringstream tagcontent;
            tagcontent << *solverInfo;
            // read the property name
            ASSERTL0(solverInfo->Attribute("PROPERTY"),
                     "Missing PROPERTY attribute in solver info "
                     "XML element: \n\t'" +
                         tagcontent.str() + "'");
            std::string solverProperty = solverInfo->Attribute("PROPERTY");
            ASSERTL0(!solverProperty.empty(),
                     "PROPERTY attribute must be non-empty in XML "
                     "element: \n\t'" +
                         tagcontent.str() + "'");

            // make sure that solver property is capitalised
            std::string solverPropertyUpper =
                boost::to_upper_copy(solverProperty);

            // read the value
            ASSERTL0(solverInfo->Attribute("VALUE"),
                     "Missing VALUE attribute in solver info "
                     "XML element: \n\t'" +
                         tagcontent.str() + "'");
            std::string solverValue = solverInfo->Attribute("VALUE");
            ASSERTL0(!solverValue.empty(),
                     "VALUE attribute must be non-empty in XML "
                     "element: \n\t'" +
                         tagcontent.str() + "'");

            // Set Variable
            m_solverInfo[solverPropertyUpper] = solverValue;
            solverInfo = solverInfo->NextSiblingElement("I");
        }
    }

    if (m_comm && m_comm->GetRowComm()->GetSize() > 1)
    {
        ASSERTL0(
            m_solverInfo["GLOBALSYSSOLN"] == "IterativeFull" ||
                m_solverInfo["GLOBALSYSSOLN"] == "IterativeStaticCond" ||
                m_solverInfo["GLOBALSYSSOLN"] ==
                    "IterativeMultiLevelStaticCond" ||
                m_solverInfo["GLOBALSYSSOLN"] == "XxtFull" ||
                m_solverInfo["GLOBALSYSSOLN"] == "XxtStaticCond" ||
                m_solverInfo["GLOBALSYSSOLN"] == "XxtMultiLevelStaticCond" ||
                m_solverInfo["GLOBALSYSSOLN"] == "PETScFull" ||
                m_solverInfo["GLOBALSYSSOLN"] == "PETScStaticCond" ||
                m_solverInfo["GLOBALSYSSOLN"] == "PETScMultiLevelStaticCond",
            "A parallel solver must be used when run in parallel.");
    }
}

/**
 *
 */
void SessionReader::ReadGlobalSysSolnInfo(TiXmlElement *conditions)
{
    GetGloSysSolnList().clear();

    if (!conditions)
    {
        return;
    }

    TiXmlElement *GlobalSys =
        conditions->FirstChildElement("GLOBALSYSSOLNINFO");
    GetXMLElementTimeLevel(GlobalSys, m_timeLevel);

    if (!GlobalSys)
    {
        return;
    }

    TiXmlElement *VarInfo = GlobalSys->FirstChildElement("V");

    while (VarInfo)
    {
        std::stringstream tagcontent;
        tagcontent << *VarInfo;

        ASSERTL0(VarInfo->Attribute("VAR"),
                 "Missing VAR attribute in GobalSysSolnInfo XML "
                 "element: \n\t'" +
                     tagcontent.str() + "'");
        std::string VarList = VarInfo->Attribute("VAR");
        ASSERTL0(!VarList.empty(),
                 "VAR attribute must be non-empty in XML element:\n\t'" +
                     tagcontent.str() + "'");

        // generate a list of variables.
        std::vector<std::string> varStrings;
        bool valid = ParseUtils::GenerateVector(VarList, varStrings);

        ASSERTL0(valid, "Unable to process list of variable in XML "
                        "element \n\t'" +
                            tagcontent.str() + "'");

        if (varStrings.size())
        {
            TiXmlElement *SysSolnInfo = VarInfo->FirstChildElement("I");

            while (SysSolnInfo)
            {
                tagcontent.clear();
                tagcontent << *SysSolnInfo;
                // read the property name
                ASSERTL0(SysSolnInfo->Attribute("PROPERTY"),
                         "Missing PROPERTY attribute in "
                         "GlobalSysSolnInfo for variable(s) '" +
                             VarList + "' in XML element: \n\t'" +
                             tagcontent.str() + "'");
                std::string SysSolnProperty =
                    SysSolnInfo->Attribute("PROPERTY");
                ASSERTL0(!SysSolnProperty.empty(),
                         "GlobalSysSolnIno properties must have a "
                         "non-empty name for variable(s) : '" +
                             VarList + "' in XML element: \n\t'" +
                             tagcontent.str() + "'");

                // make sure that solver property is capitalised
                std::string SysSolnPropertyUpper =
                    boost::to_upper_copy(SysSolnProperty);

                // read the value
                ASSERTL0(SysSolnInfo->Attribute("VALUE"),
                         "Missing VALUE attribute in GlobalSysSolnInfo "
                         "for variable(s) '" +
                             VarList + "' in XML element: \n\t" +
                             tagcontent.str() + "'");
                std::string SysSolnValue = SysSolnInfo->Attribute("VALUE");
                ASSERTL0(!SysSolnValue.empty(),
                         "GlobalSysSolnInfo properties must have a "
                         "non-empty value for variable(s) '" +
                             VarList + "' in XML element: \n\t'" +
                             tagcontent.str() + "'");

                // Store values under variable map.
                for (int i = 0; i < varStrings.size(); ++i)
                {
                    auto x = GetGloSysSolnList().find(varStrings[i]);
                    if (x == GetGloSysSolnList().end())
                    {
                        (GetGloSysSolnList()[varStrings[i]])
                            [SysSolnPropertyUpper] = SysSolnValue;
                    }
                    else
                    {
                        x->second[SysSolnPropertyUpper] = SysSolnValue;
                    }
                }
                SysSolnInfo = SysSolnInfo->NextSiblingElement("I");
            }
            VarInfo = VarInfo->NextSiblingElement("V");
        }
    }

    if (m_verbose && GetGloSysSolnList().size() > 0 && m_comm)
    {
        if (m_comm->GetRank() == 0)
        {
            cout << "GlobalSysSoln Info:" << endl;

            for (auto &x : GetGloSysSolnList())
            {
                cout << "\t Variable: " << x.first << endl;

                for (auto &y : x.second)
                {
                    cout << "\t\t " << y.first << " = " << y.second << endl;
                }
            }
            cout << endl;
        }
    }
}

/**
 * @brief Read the time-integration scheme structure, if present.
 */
void SessionReader::ReadTimeIntScheme(TiXmlElement *conditions)
{
    if (!conditions)
    {
        return;
    }

    TiXmlElement *timeInt =
        conditions->FirstChildElement("TIMEINTEGRATIONSCHEME");
    GetXMLElementTimeLevel(timeInt, m_timeLevel);

    if (!timeInt)
    {
        return;
    }

    TiXmlElement *method  = timeInt->FirstChildElement("METHOD");
    TiXmlElement *variant = timeInt->FirstChildElement("VARIANT");
    TiXmlElement *order   = timeInt->FirstChildElement("ORDER");
    TiXmlElement *params  = timeInt->FirstChildElement("FREEPARAMETERS");

    // Only the method and order are required.
    ASSERTL0(method, "Missing METHOD tag inside "
                     "TIMEINTEGRATIONSCHEME.");
    ASSERTL0(order, "Missing ORDER tag inside "
                    "TIMEINTEGRATIONSCHEME.");

    m_timeIntScheme.method = method->GetText();

    std::string orderStr = order->GetText();

    // Only the method and order are required.
    ASSERTL0(m_timeIntScheme.method.size() > 0,
             "Empty text inside METHOD tag in TIMEINTEGRATIONSCHEME.");
    ASSERTL0(orderStr.size() > 0,
             "Empty text inside ORDER tag in TIMEINTEGRATIONSCHEME.");
    try
    {
        m_timeIntScheme.order = std::stol(orderStr);
    }
    catch (...)
    {
        NEKERROR(ErrorUtil::efatal, "In ORDER tag, unable to convert "
                                    "string '" +
                                        orderStr + "' to an unsigned integer.");
    }

    if (variant)
    {
        m_timeIntScheme.variant = variant->GetText();
    }

    if (params)
    {
        std::string paramsStr = params->GetText();
        ASSERTL0(paramsStr.size() > 0,
                 "Empty text inside FREEPARAMETERS tag in "
                 "TIMEINTEGRATIONSCHEME.");

        std::vector<std::string> pSplit;
        boost::split(pSplit, paramsStr, boost::is_any_of(" "));

        m_timeIntScheme.freeParams.resize(pSplit.size());
        for (size_t i = 0; i < pSplit.size(); ++i)
        {
            try
            {
                m_timeIntScheme.freeParams[i] = std::stod(pSplit[i]);
            }
            catch (...)
            {
                NEKERROR(ErrorUtil::efatal, "In FREEPARAMETERS tag, "
                                            "unable to convert string '" +
                                                pSplit[i] +
                                                "' "
                                                "to a floating-point value.");
            }
        }
    }

    if (m_verbose && m_comm)
    {
        if (m_comm->GetRank() == 0)
        {
            cout << "Trying to use time integration scheme:" << endl;
            cout << "\t Method : " << m_timeIntScheme.method << endl;
            cout << "\t Variant: " << m_timeIntScheme.variant << endl;
            cout << "\t Order  : " << m_timeIntScheme.order << endl;

            if (m_timeIntScheme.freeParams.size() > 0)
            {
                cout << "\t Params :";
                for (auto &x : m_timeIntScheme.freeParams)
                {
                    cout << " " << x;
                }
                cout << endl;
            }
        }
    }
}

/**
 *
 */
void SessionReader::ReadVariables(TiXmlElement *conditions)
{
    m_variables.clear();

    if (!conditions)
    {
        return;
    }

    TiXmlElement *variablesElement = conditions->FirstChildElement("VARIABLES");
    GetXMLElementTimeLevel(variablesElement, m_timeLevel);

    // See if we have parameters defined. They are optional so we go on
    // if not.
    if (variablesElement)
    {
        TiXmlElement *varElement = variablesElement->FirstChildElement("V");

        // Sequential counter for the composite numbers.
        int nextVariableNumber = -1;

        while (varElement)
        {
            stringstream tagcontent;
            tagcontent << *varElement;

            /// All elements are of the form: "<V ID="#"> name = value
            /// </V>", with ? being the element type.
            nextVariableNumber++;

            int i;
            int err = varElement->QueryIntAttribute("ID", &i);
            ASSERTL0(err == TIXML_SUCCESS,
                     "Variables must have a unique ID number attribute "
                     "in XML element: \n\t'" +
                         tagcontent.str() + "'");
            ASSERTL0(i == nextVariableNumber,
                     "ID numbers for variables must begin with zero and"
                     " be sequential in XML element: \n\t'" +
                         tagcontent.str() + "'");

            TiXmlNode *varChild = varElement->FirstChild();
            // This is primarily to skip comments that may be present.
            // Comments appear as nodes just like elements.  We are
            // specifically looking for text in the body of the
            // definition.
            while (varChild && varChild->Type() != TiXmlNode::TINYXML_TEXT)
            {
                varChild = varChild->NextSibling();
            }

            ASSERTL0(varChild, "Unable to read variable definition body for "
                               "variable with ID " +
                                   std::to_string(i) +
                                   " in XML element: \n\t'" + tagcontent.str() +
                                   "'");
            std::string variableName = varChild->ToText()->ValueStr();

            std::istringstream variableStrm(variableName);
            variableStrm >> variableName;

            ASSERTL0(std::find(m_variables.begin(), m_variables.end(),
                               variableName) == m_variables.end(),
                     "Variable with ID " + std::to_string(i) +
                         " in XML element \n\t'" + tagcontent.str() +
                         "'\nhas already been defined.");

            m_variables.push_back(variableName);

            varElement = varElement->NextSiblingElement("V");
        }

        ASSERTL0(nextVariableNumber > -1,
                 "Number of variables must be greater than zero.");
    }
}

/**
 *
 */
void SessionReader::ReadFunctions(TiXmlElement *conditions)
{
    m_functions.clear();

    if (!conditions)
    {
        return;
    }

    // Scan through conditions section looking for functions.
    TiXmlElement *function = conditions->FirstChildElement("FUNCTION");

    while (function)
    {
        stringstream tagcontent;
        tagcontent << *function;

        // Every function must have a NAME attribute
        ASSERTL0(function->Attribute("NAME"),
                 "Functions must have a NAME attribute defined in XML "
                 "element: \n\t'" +
                     tagcontent.str() + "'");
        std::string functionStr = function->Attribute("NAME");
        ASSERTL0(!functionStr.empty(),
                 "Functions must have a non-empty name in XML "
                 "element: \n\t'" +
                     tagcontent.str() + "'");

        // Store function names in uppercase to remain case-insensitive.
        boost::to_upper(functionStr);

        // Retrieve first entry (variable, or file)
        TiXmlElement *element = function;
        GetXMLElementTimeLevel(element, m_timeLevel, false);
        TiXmlElement *variable = element->FirstChildElement();

        // Create new function structure with default type of none.
        FunctionVariableMap functionVarMap;

        // Process all entries in the function block
        while (variable)
        {
            FunctionVariableDefinition funcDef;
            std::string conditionType = variable->Value();

            // If no var is specified, assume wildcard
            std::string variableStr;
            if (!variable->Attribute("VAR"))
            {
                variableStr = "*";
            }
            else
            {
                variableStr = variable->Attribute("VAR");
            }

            // Parse list of variables
            std::vector<std::string> variableList;
            ParseUtils::GenerateVector(variableStr, variableList);

            // If no domain is specified, put to 0
            std::string domainStr;
            if (!variable->Attribute("DOMAIN"))
            {
                domainStr = "0";
            }
            else
            {
                domainStr = variable->Attribute("DOMAIN");
            }

            // Parse list of domains
            std::vector<std::string> varSplit;
            std::vector<unsigned int> domainList;
            ParseUtils::GenerateSeqVector(domainStr, domainList);

            // if no evars is specified, put "x y z t"
            std::string evarsStr = "x y z t";
            if (variable->Attribute("EVARS"))
            {
                evarsStr =
                    evarsStr + std::string(" ") + variable->Attribute("EVARS");
            }

            // Expressions are denoted by E
            if (conditionType == "E")
            {
                funcDef.m_type = eFunctionTypeExpression;

                // Expression must have a VALUE.
                ASSERTL0(variable->Attribute("VALUE"),
                         "Attribute VALUE expected for function '" +
                             functionStr + "'.");
                std::string fcnStr = variable->Attribute("VALUE");
                ASSERTL0(!fcnStr.empty(),
                         (std::string("Expression for var: ") + variableStr +
                          std::string(" must be specified."))
                             .c_str());

                // set expression
                funcDef.m_expression =
                    MemoryManager<Equation>::AllocateSharedPtr(
                        m_interpreter, fcnStr, evarsStr);
            }

            // Files are denoted by F
            else if (conditionType == "F")
            {
                // Check if transient or not
                if (variable->Attribute("TIMEDEPENDENT") &&
                    boost::lexical_cast<bool>(
                        variable->Attribute("TIMEDEPENDENT")))
                {
                    funcDef.m_type = eFunctionTypeTransientFile;
                }
                else
                {
                    funcDef.m_type = eFunctionTypeFile;
                }

                // File must have a FILE.
                ASSERTL0(variable->Attribute("FILE"),
                         "Attribute FILE expected for function '" +
                             functionStr + "'.");
                std::string filenameStr = variable->Attribute("FILE");
                ASSERTL0(!filenameStr.empty(),
                         "A filename must be specified for the FILE "
                         "attribute of function '" +
                             functionStr + "'.");

                std::vector<std::string> fSplit;
                boost::split(fSplit, filenameStr, boost::is_any_of(":"));
                ASSERTL0(fSplit.size() == 1 || fSplit.size() == 2,
                         "Incorrect filename specification in function " +
                             functionStr +
                             "'. "
                             "Specify variables inside file as: "
                             "filename:var1,var2");

                // set the filename
                fs::path fullpath = fSplit[0];
                fs::path ftype    = fullpath.extension();
                if (fullpath.parent_path().extension() == ".pit")
                {
                    string filename = fullpath.stem().string();
                    fullpath        = fullpath.parent_path();
                    size_t start    = filename.find_last_of("_") + 1;
                    int index =
                        atoi(filename.substr(start, filename.size()).c_str());
                    fullpath /= filename.substr(0, start) +
                                std::to_string(
                                    index + m_comm->GetTimeComm()->GetRank()) +
                                ftype.string();
                }
                funcDef.m_filename = fullpath.string();

                if (fSplit.size() == 2)
                {
                    ASSERTL0(variableList[0] != "*",
                             "Filename variable mapping not valid "
                             "when using * as a variable inside "
                             "function '" +
                                 functionStr + "'.");

                    boost::split(varSplit, fSplit[1], boost::is_any_of(","));
                    ASSERTL0(varSplit.size() == variableList.size(),
                             "Filename variables should contain the "
                             "same number of variables defined in "
                             "VAR in function " +
                                 functionStr + "'.");
                }
            }

            // Nothing else supported so throw an error
            else
            {
                stringstream tagcontent;
                tagcontent << *variable;

                NEKERROR(ErrorUtil::efatal,
                         "Identifier " + conditionType + " in function " +
                             std::string(function->Attribute("NAME")) +
                             " is not recognised in XML element: \n\t'" +
                             tagcontent.str() + "'");
            }

            // Add variables to function
            for (unsigned int i = 0; i < variableList.size(); ++i)
            {
                for (unsigned int j = 0; j < domainList.size(); ++j)
                {
                    // Check it has not already been defined
                    pair<std::string, int> key(variableList[i], domainList[j]);
                    auto fcnsIter = functionVarMap.find(key);
                    ASSERTL0(fcnsIter == functionVarMap.end(),
                             "Error setting expression '" + variableList[i] +
                                 " in domain " + std::to_string(domainList[j]) +
                                 "' in function '" + functionStr +
                                 "'. "
                                 "Expression has already been defined.");

                    if (varSplit.size() > 0)
                    {
                        FunctionVariableDefinition funcDef2 = funcDef;
                        funcDef2.m_fileVariable             = varSplit[i];
                        functionVarMap[key]                 = funcDef2;
                    }
                    else
                    {
                        functionVarMap[key] = funcDef;
                    }
                }
            }
            variable = variable->NextSiblingElement();
        }

        // Add function definition to map
        m_functions[functionStr] = functionVarMap;
        function                 = function->NextSiblingElement("FUNCTION");
    }
}

/**
 *
 */
void SessionReader::ReadFilters(TiXmlElement *filters)
{
    if (!filters)
    {
        return;
    }

    m_filters.clear();

    TiXmlElement *filter = filters->FirstChildElement("FILTER");

    while (filter)
    {
        ASSERTL0(filter->Attribute("TYPE"),
                 "Missing attribute 'TYPE' for filter.");
        std::string typeStr = filter->Attribute("TYPE");

        int domainID = -1;
        if (filter->Attribute("DOMAIN"))
        {
            // domainID = int(filter->Attribute("DOMAIN"));
            int err = filter->QueryIntAttribute("DOMAIN", &domainID);
            ASSERTL0(err == TIXML_SUCCESS,
                     "Unable to read attribute DOMAIN in filter.");
        }

        std::map<std::string, std::string> vParams;

        TiXmlElement *element = filter;
        GetXMLElementTimeLevel(element, m_timeLevel, false);
        TiXmlElement *param = element->FirstChildElement("PARAM");
        while (param)
        {
            ASSERTL0(param->Attribute("NAME"),
                     "Missing attribute 'NAME' for parameter in filter " +
                         typeStr + "'.");
            std::string nameStr = param->Attribute("NAME");

            ASSERTL0(param->GetText(), "Empty value string for param.");
            std::string valueStr = param->GetText();

            vParams[nameStr] = valueStr;

            param = param->NextSiblingElement("PARAM");
        }

        FilterDefinition filterDef;
        filterDef.domain = domainID;
        filterDef.name   = typeStr;
        filterDef.params = vParams;
        m_filters.push_back(filterDef);

        filter = filter->NextSiblingElement("FILTER");
    }
}

/**
 *
 */
void SessionReader::ParseEquals(const std::string &line, std::string &lhs,
                                std::string &rhs)
{
    /// Pull out lhs and rhs and eliminate any spaces.
    size_t beg = line.find_first_not_of(" ");
    size_t end = line.find_first_of("=");
    // Check for no parameter name
    if (beg == end)
    {
        throw 1;
    }
    // Check for no parameter value
    if (end != line.find_last_of("="))
    {
        throw 1;
    }
    // Check for no equals sign
    if (end == std::string::npos)
    {
        throw 1;
    }

    lhs = line.substr(line.find_first_not_of(" "), end - beg);
    lhs = lhs.substr(0, lhs.find_last_not_of(" ") + 1);
    rhs = line.substr(line.find_last_of("=") + 1);
    rhs = rhs.substr(rhs.find_first_not_of(" "));
    rhs = rhs.substr(0, rhs.find_last_not_of(" ") + 1);
}

/**
 *
 */
void SessionReader::CmdLineOverride()
{
    // Parse solver info overrides
    if (m_cmdLineOptions.count("solverinfo"))
    {
        std::vector<std::string> solverInfoList =
            m_cmdLineOptions["solverinfo"].as<std::vector<std::string>>();

        for (size_t i = 0; i < solverInfoList.size(); ++i)
        {
            std::string lhs, rhs;

            try
            {
                ParseEquals(solverInfoList[i], lhs, rhs);
            }
            catch (...)
            {
                NEKERROR(ErrorUtil::efatal, "Parse error with command line "
                                            "option: " +
                                                solverInfoList[i]);
            }

            std::string lhsUpper   = boost::to_upper_copy(lhs);
            m_solverInfo[lhsUpper] = rhs;
        }
    }

    if (m_cmdLineOptions.count("parameter"))
    {
        std::vector<std::string> parametersList =
            m_cmdLineOptions["parameter"].as<std::vector<std::string>>();

        for (size_t i = 0; i < parametersList.size(); ++i)
        {
            std::string lhs, rhs;

            try
            {
                ParseEquals(parametersList[i], lhs, rhs);
            }
            catch (...)
            {
                NEKERROR(ErrorUtil::efatal, "Parse error with command line "
                                            "option: " +
                                                parametersList[i]);
            }

            std::string lhsUpper = boost::to_upper_copy(lhs);

            try
            {
                m_parameters[lhsUpper] = std::stod(rhs);
            }
            catch (...)
            {
                NEKERROR(ErrorUtil::efatal, "Unable to convert string: " + rhs +
                                                "to double value.");
            }
        }
    }
}

/**
 *
 */
void SessionReader::VerifySolverInfo()
{
    for (auto &x : m_solverInfo)
    {
        std::string solverProperty = x.first;
        std::string solverValue    = x.second;

        auto propIt = GetSolverInfoEnums().find(solverProperty);
        if (propIt != GetSolverInfoEnums().end())
        {
            auto valIt = propIt->second.find(solverValue);
            ASSERTL0(valIt != propIt->second.end(), "Value '" + solverValue +
                                                        "' is not valid for "
                                                        "property '" +
                                                        solverProperty + "'");
        }
    }
}

/**
 *
 */
InterpreterSharedPtr SessionReader::GetInterpreter()
{
    return m_interpreter;
}

/**
 * Helper function that gets a pointer to a child element, or throws an
 * exception if no such element exists
 */
TiXmlElement *GetChildElementOrThrow(const std::string &filename,
                                     std::string elementName,
                                     const TiXmlHandle &docHandle)
{
    TiXmlElement *element = docHandle.FirstChildElement(elementName).Element();

    if (!element)
    {
        NEKERROR(ErrorUtil::efatal, "Unable to find '" + elementName +
                                        "' XML node in " + filename);
    }

    return element;
}

} // namespace Nektar::LibUtilities
