Program Listing for File input.cpp

Return to documentation for file (input.cpp)

// ***********************************************************************************
// Idefix MHD astrophysical code
// Copyright(C) 2020-2022 Geoffroy R. J. Lesur <geoffroy.lesur@univ-grenoble-alpes.fr>
// and other code contributors
// Licensed under CeCILL 2.1 License, see COPYING for more information
// ***********************************************************************************

#include <dirent.h>

#include <fstream>
#include <string>
#include <csignal>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#include <memory>

#include "idefix.hpp"
#include "input.hpp"
#include "gitversion.hpp"

// Flag will be set if a signal has been received
bool Input::abortRequested = false;

Input::Input() {
}

// Create input from file filename
// This routine expects input file of the following form:
// [Blockname]                                # comments2
// Parameter_name   Parametervalue1   Parametervalue2  Parametervalue3... # comments1
//
// Comments are allowed everywhere. Anything after # is ignored in the line
// Blockname should refer to one of Idefix class which will use the parameters
// in said block. Everything is stored in a map of maps of vectors of strings :-)

Input::Input(int argc, char* argv[] ) {
  std::ifstream file;
  std::string line, lineWithComments, blockName, paramName, paramValue;
  std::size_t firstChar, lastChar;
  bool haveBlock = false;
  std::stringstream msg;
  int nParameters = 0;    // # of parameters in current block

  // Tell the system we want to catch the SIGUSR2 signals
  signal(SIGUSR2, signalHandler);

  // Tell the time when input was initialised
  timer.reset();
  lastStopFileCheck = timer.seconds();

  // Default input file name
  this->inputFileName = std::string("idefix.ini");

  // Parse command line (may replace the input file)
  ParseCommandLine(argc,argv);

  file.open(this->inputFileName);

  if(!file) {
    msg << "Input: cannot open input file " << this->inputFileName;
    IDEFIX_ERROR(msg);
  }

  while(std::getline(file, lineWithComments)) {
    line = lineWithComments.substr(0, lineWithComments.find("#",0));
    if (line.empty()) continue;     // skip blank line
    firstChar = line.find_first_not_of(" ");
    if (firstChar == std::string::npos) continue;      // line is all white space

    if (line.compare(firstChar, 1, "[") == 0) {        // a new block
      firstChar++;
      lastChar = (line.find_first_of("]", firstChar));

      if (lastChar == std::string::npos) {
        msg << "Block name '" << blockName << "' in file '"
            << this->inputFileName << "' not properly ended";
        IDEFIX_ERROR(msg);
      }
      // Check if previous block was empty
      if(haveBlock && nParameters == 0) {
        IDEFIX_WARNING(blockName+std::string(" block is empty in "+inputFileName));
        inputParameters[blockName]["!!empty!!"].push_back("!!empty!!");
      }
      blockName.assign(line, firstChar, lastChar-1);
      haveBlock = true;
      nParameters = 0;

      continue;   // Go to next line
    }   // New block

    // At this point, we should have a parameter set in the line
    if(haveBlock == false) {
      msg << "Input file '" << this->inputFileName
          << "' must specify a block name before the first parameter";
      IDEFIX_ERROR(msg);
    }

    std::stringstream streamline(line);
    // Store the name of the parameter
    streamline >> paramName;
    nParameters++;
    // Store the parameters in parameter block
    while(streamline >> paramValue) {
      inputParameters[blockName][paramName].push_back(paramValue);
    }
  }
  file.close();
}

// This routine parse command line options
void Input::ParseCommandLine(int argc, char **argv) {
  std::stringstream msg;
  bool enableLogs = true;
  for(int i = 1 ; i < argc ; i++) {
    // MPI decomposition argument
    if(std::string(argv[i]) == "-dec") {
      #ifndef WITH_MPI
      IDEFIX_ERROR("Domain decomposition option '-dec' only makes sense when MPI is enabled");
      #endif
      // Loop on dimensions
      for(int dir = 0 ; dir < DIMENSIONS ; dir++) {
        if ((++i) >= argc) {
          D_SELECT(msg << "You must specify -dec n1";  ,
              msg << "You must specify -dec n1 n2";  ,
              msg << "You must specify -dec n1 n2 n3"; )
          IDEFIX_ERROR(msg);
        }
        // Store this
        inputParameters["CommandLine"]["dec"].push_back(std::string(argv[i]));
      }
    } else if(std::string(argv[i]) == "-restart") {
      std::string sirestart{};
      bool explicitDump = true;     // by default, assume a restart dump # was given
      // Check whether -restart was given with a number or not

      // -restart was the very last parameter
      if((i+1)>= argc) {
        explicitDump = false;
      } else if(std::isdigit(argv[i+1][0]) == 0) {
        // next argiment is another parameter (does not start with a number)
        explicitDump = false;
      }

      if(explicitDump) {
        sirestart = std::string(argv[++i]);
      } else {
        // implicitly restart from the latest existing dumpfile
        // implementation detail: we look for the existing dumpfile with the highest
        // number, not necessarilly the latest timestamp !
        const std::vector<std::string> files = Input::getDirectoryFiles();
        int ifile{-1};
        int irestart{-1};

        for (const auto& file : files) {
          if (Input::getFileExtension(file).compare("dmp") != 0) continue;
          // parse the dumpfile number from filename "dump.????.dmp"
          if(file.substr(0,5) != "dump.") continue;
          try {
            ifile = std::stoi(file.substr(5, 4));
          } catch (...) {
            // woops, pattern doesn't match!
            ifile = -1;
          }
          irestart = std::max(irestart, ifile);
        }
        sirestart = std::to_string(irestart);
        if(irestart==-1) {
          IDEFIX_WARNING("cannot find a valid restart dump file in current directory");
        }
      }
      int restartn = std::stoi(sirestart);
      if(restartn>=0) {
        inputParameters["CommandLine"]["restart"].push_back(sirestart);
        this->restartRequested = true;
        this->restartFileNumber = restartn;
      } else {
        IDEFIX_WARNING("Invalid -restart option, I will ignore it.");
      }
    } else if(std::string(argv[i]) == "-i") {
      // Loop on dimensions
      if((++i) >= argc) IDEFIX_ERROR(
                      "You must specify -i filename where filename is the name of the input file.");
      this->inputFileName = std::string(argv[i]);
    } else if(std::string(argv[i]) == "-maxcycles") {
      if((i+1)>= argc) {
        IDEFIX_ERROR("-maxcycles requires an additional integer parameter");
      } else if(std::isdigit(argv[i+1][0]) == 0) {
        // next argiment is another parameter (does not start with a number)
        IDEFIX_ERROR("-maxcycles requires an additional integer paramater");
      }
      this->maxCycles = std::stoi(std::string(argv[++i]));
      inputParameters["CommandLine"]["maxCycles"].push_back(std::to_string(maxCycles));
    } else if(std::string(argv[i]) == "-nowrite") {
      this->forceNoWrite = true;
      enableLogs = false;
    } else if(std::string(argv[i]) == "-nolog") {
      enableLogs = false;
    } else if(std::string(argv[i]) == "-Werror") {
      idfx::warningsAreErrors = true;
    } else {
      msg << "Unknown option " << argv[i];
      IDEFIX_ERROR(msg);
    }
  }
  if(enableLogs) {
    idfx::cout.enableLogFile();
  }
}


// This routine prints the parameters stored in the inputParameters structure
void Input::ShowConfig() {
  std::string blockName, paramName, paramValue;
  idfx::cout << "-----------------------------------------------------------------------------"
             << std::endl;
  idfx::cout << "Input Parameters using input file " << this->inputFileName << ":" << std::endl;
  idfx::cout << "-----------------------------------------------------------------------------"
             << std::endl;
  for(IdefixInputContainer::iterator block = inputParameters.begin();
      block != inputParameters.end();
      block++ ) {
    blockName=block->first;
    idfx::cout << "[" << blockName << "]" << std::endl;
    for(IdefixBlockContainer::iterator param = block->second.begin();
          param !=block->second.end(); param++) {
      paramName=param->first;
      idfx::cout << "\t" << paramName << "\t";
      for(IdefixParamContainer::iterator value = param->second.begin();
          value != param->second.end(); value++) {
        paramValue = *value;
        idfx::cout << "\t" << paramValue;
      }
      idfx::cout << std::endl;
    }
  }
  idfx::cout << "-----------------------------------------------------------------------------"
             << std::endl;
  idfx::cout << "-----------------------------------------------------------------------------"
             << std::endl;

  #ifdef SINGLE_PRECISION
    idfx::cout << "Input: Compiled with SINGLE PRECISION arithmetic." << std::endl;
  #else
    idfx::cout << "Input: Compiled with DOUBLE PRECISION arithmetic." << std::endl;
  #endif
  // Show dimensionality and other general options:
  idfx::cout << "Input: DIMENSIONS=" << DIMENSIONS << "." << std::endl;
  idfx::cout << "Input: COMPONENTS=" << COMPONENTS << "." << std::endl;
  #ifdef WITH_MPI
    idfx::cout << "Input: MPI ENABLED." << std::endl;
  #endif
  #ifdef KOKKOS_ENABLE_HIP
    idfx::cout << "Input: Kokkos HIP target ENABLED." << std::endl;
  #endif
  #ifdef KOKKOS_ENABLE_CUDA
    idfx::cout << "Input: Kokkos CUDA target ENABLED." << std::endl;
  #endif
  #ifdef KOKKOS_ENABLE_OPENMP
    idfx::cout << "Input: Kokkos OpenMP ENABLED." << std::endl;
  #endif
}

// This routine is called whenever a specific OS signal is caught
void Input::signalHandler(int signum) {
  idfx::cout << std::endl << "Input: Caught interrupt " << signum << std::endl;
  abortRequested=true;
}

void Input::CheckForStopFile() {
  // Check whether a file "stop" has been created in directory. If so, raise the abort flag
  // Look for stop file every 5 seconds to avoid overloading the file system
  if(idfx::prank==0) {
    std::string filename = std::string("stop");
    if(timer.seconds()-lastStopFileCheck>5.0) {
      lastStopFileCheck = timer.seconds();
      std::ifstream f(filename);
      if(f.good()) {
        // File exists, delete it and raise the flag
        std::remove(filename.c_str());
        abortRequested = true;
        idfx::cout << std::endl << "Input: Caught stop file command" << std::endl;
      }
    }
  }
}

bool Input::CheckForAbort() {
  idfx::pushRegion("Input::CheckForAbort");
  // Check whether an abort has been requesested
  // When MPI is present, we abort whenever one process got the signal
  CheckForStopFile();
#ifdef WITH_MPI
  int abortValue{0};
  bool returnValue{false};
  if(abortRequested) abortValue = 1;

  MPI_Bcast(&abortValue, 1, MPI_INT, 0, MPI_COMM_WORLD);
  returnValue = abortValue > 0;
  if(returnValue) idfx::cout << "Input: CheckForAbort: abort has been requested." << std::endl;
  idfx::popRegion();
  return(returnValue);
#else
  if(abortRequested) idfx::cout << "Input: CheckForAbort: abort has been requested." << std::endl;
  idfx::popRegion();
  return(abortRequested);
#endif
}

std::vector<std::string> Input::getDirectoryFiles() {
  // List files in the current directory
  // adapted from
  // http://www.codebind.com/cpp-tutorial/cpp-program-list-files-directory-windows-linux/
  const std::string& dir = std::string(".");
  std::vector<std::string> files;
  std::shared_ptr<DIR> directory_ptr(opendir(dir.c_str()), [](DIR* dir){ dir && closedir(dir); });
  struct dirent *dirent_ptr;
  if (!directory_ptr) {
    idfx::cout << "Error opening : " << std::strerror(errno) << dir << std::endl;
    return files;
  }

  while ((dirent_ptr = readdir(directory_ptr.get())) != nullptr) {
    files.push_back(std::string(dirent_ptr->d_name));
  }
  return files;
}


std::string Input::getFileExtension(const std::string file_name) {
  int position = file_name.find_last_of(".");
  std::string ext = file_name.substr(position+1);
  return ext;
}

// Get a string in a block, parameter, position of the file
std::string Input::GetString(std::string blockName, std::string paramName, int num) {
  IDEFIX_DEPRECATED("Input::GetString is deprecated. Use Input::Get<std::string> instead");
  return(Get<std::string>(blockName, paramName, num));
}

// Get a real number in a block, parameter, position of the file
real Input::GetReal(std::string blockName, std::string paramName, int num) {
  IDEFIX_DEPRECATED("Input::GetReal is deprecated. Use Input::Get<real> instead");
  return(Get<real>(blockName, paramName, num));
}

// Get an integer number in a block, parameter, position of the file
int Input::GetInt(std::string blockName, std::string paramName, int num) {
  IDEFIX_DEPRECATED("Input::GetInt is deprecated. Use Input::Get<int> instead");
  return(Get<int>(blockName, paramName, num));
}

// Check that an entry is present in the ini file.
// If yes, return the number of parameters for given entry
int Input::CheckEntry(std::string blockName, std::string paramName) {
  int result=-1;
  IdefixInputContainer::iterator block = inputParameters.find(blockName);
  if(block != inputParameters.end()) {
    // Block exists
    IdefixBlockContainer::iterator param = block->second.find(paramName);
    if(param != block->second.end()) {
      // Parameter exist
      result = param->second.size();
    }
  }
  return(result);
}

// Check that a block is present in the ini file.
// If yes, return true
bool Input::CheckBlock(std::string blockName) {
  bool result = false;
  IdefixInputContainer::iterator block = inputParameters.find(blockName);
  if(block != inputParameters.end()) {
    // Block exists
    result = true;
  }
  return(result);
}

void Input::PrintLogo() {
  idfx::cout << "                                  .:HMMMMHn:.  ..:n.."<< std::endl;
  idfx::cout << "                                .H*'``     `'%HM'''''!x."<< std::endl;
  idfx::cout << "         :x                    x*`           .(MH:    `#h."<< std::endl;
  idfx::cout << "        x.`M                   M>        :nMMMMMMMh.     `n."<< std::endl;
  idfx::cout << "         *kXk..                XL  nnx:.XMMMMMMMMMMML   .. 4X."<< std::endl;
  idfx::cout << "          )MMMMMx              'M   `^?M*MMMMMMMMMMMM:HMMMHHMM."<< std::endl;
  idfx::cout << "          MMMMMMMX              ?k    'X ..'*MMMMMMM.#MMMMMMMMMx"<< std::endl;
  idfx::cout << "         XMMMMMMMX               4:    M:MhHxxHHHx`MMx`MMMMMMMMM>"<< std::endl;
  idfx::cout << "         XM!`   ?M                `x   4MM'`''``HHhMMX  'MMMMMMMM"<< std::endl;
  idfx::cout << "         4M      M                 `:   *>     `` .('MX   '*MMMM'"<< std::endl;
  idfx::cout << "          MX     `X.nnx..                        ..XMx`     'M*X"<< std::endl;
  idfx::cout << "           ?h.    ''```^'*!Hx.     :Mf     xHMh  M**MMM      4L`"<< std::endl;
  idfx::cout << "            `*Mx           `'*n.x. 4M>   :M` `` 'M    `       %"<< std::endl;
  idfx::cout << "             '%                ``*MHMX   X>      !"<< std::endl;
  idfx::cout << "            :!                    `#MM>  X>      `   :x"<< std::endl;
  idfx::cout << "           :M                        ?M  `X     .  ..'M"<< std::endl;
  idfx::cout << "           XX                       .!*X  `x   XM( MMx`h"<< std::endl;
  idfx::cout << "          'M>::                        `M: `+  MMX XMM `:"<< std::endl;
  idfx::cout << "          'M> M                         'X    'MMX ?MMk.Xx.."<< std::endl;
  idfx::cout << "          'M> ?L                     ...:!     MMX.H**'MMMM*h"<< std::endl;
  idfx::cout << "           M>  #L                  :!'`MM.    . X*`.xHMMMMMnMk."<< std::endl;
  idfx::cout << "           `!   #h.      :L           XM'*hxHMM*MhHMMMMMMMMMM'#h"<< std::endl;
  idfx::cout << "           +     XMh:    4!      x   :f   MM'   `*MMMMMMMMMM%  `X"<< std::endl;
  idfx::cout << "           M     Mf``tHhxHM      M>  4k xxX'      `#MMMMMMMf    `M .>"<< std::endl;
  idfx::cout << "          :f     M   `MMMMM:     M>   M!MMM:         '*MMf'     'MH*"<< std::endl;
  idfx::cout << "          !     Xf   'MMMMMX     `X   X>'h.`          :P*Mx.   .d*~.."<< std::endl;
  idfx::cout << "        :M      X     4MMMMM>     !   X~ `Mh.      .nHL..M#'%nnMhH!'`"<< std::endl;
  idfx::cout << "       XM      d>     'X`'**h     'h  M   ^'MMHH+*'`  ''''   `'**'"<< std::endl;
  idfx::cout << "    %nxM>      *x+x.:. XL.. `k     `::X"<< std::endl;
  idfx::cout << ":nMMHMMM:.  X>  Mn`*MMMMMHM: `:     ?MMn."<< std::endl;
  idfx::cout << "    `'**MML M>  'MMhMMMMMMMM  #      `M:^*x"<< std::endl;
  idfx::cout << "         ^*MMttnnMMMMMMMMMMMH>.        M:.4X"<< std::endl;
  idfx::cout << "                        `MMMM>X   (   .MMM:MM!   ."<< std::endl;
  idfx::cout << "                          `'''4x.dX  +^ `''MMMMHM?L.."<< std::endl;
  idfx::cout << "                                ``'           `'`'`'`"<< std::endl;
  idfx::cout << std::endl;
  idfx::cout << std::endl;
  idfx::cout << std::endl;
  idfx::cout << "       This is Idefix " << GITVERSION << std::endl;
}