Commit 1a4c8903 authored by Giovanni La Mura's avatar Giovanni La Mura
Browse files

Merge branch 'prepare_MPI_parallelism' into 'master'

First working implementation of parallelism in scales over MPI

See merge request giacomo.mulas/np_tmcode!31
parents cf88c51b 375e8961
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -60,9 +60,11 @@ compatibility_stage:
      - echo "Running make with flang version 16 and clang version 16..."
      - make clean && BUILDDIR=$PWD/../build_clang16-flang16 CXX="clang++-16 -stdlib=libstdc++ -I/usr/include/c++/12 -I/usr/include/x86_64-linux-gnu/c++/12" FC=flang-new-16 FCFLAGS=-O3 LDFLAGS="-L/usr/lib/llvm-16/lib -L/usr/lib/gcc/x86_64-linux-gnu/12" make -j
      - echo "Running make with Intel ifort and Intel icpx..."
      - make clean && PATH=/opt/intel/oneapi/compiler/latest/bin:$PATH BUILDDIR=$PWD/../build_ifort-icpx CXX=icpx FC=ifort FCFLAGS="-O3 -diag-disable=10448" make -j
      - make clean && LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/2024.1/lib PATH=/opt/intel/oneapi/compiler/2024.1/bin:$PATH BUILDDIR=$PWD/../build_ifort-icpx CXX=icpx FC=ifort FCFLAGS="-O3 -diag-disable=10448" make -j
      - echo "Running make with Intel ifx and Intel icpx..."
      - make clean && LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/latest/lib PATH=/opt/intel/oneapi/compiler/latest/bin:$PATH BUILDDIR=$PWD/../build_ifx-icpx CXX=icpx FC=ifx FCFLAGS=-O3 make -j
      - make clean && LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/2024.1/lib PATH=/opt/intel/oneapi/compiler/2024.1/bin:$PATH BUILDDIR=$PWD/../build_ifx-icpx CXX=icpx FC=ifx FCFLAGS=-O3 make -j
      - echo "Running make with default MPI compilers"
      - make clean && BUILDDIR=$PWD/../build_mpi CXX=mpicxx FC=mpif90 USE_MPI=1 make -j
   
building_stage:
   stage: build
+3 −1
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ RUN apt update
RUN DEBIAN_FRONTEND=noninteractive apt -y install intel-oneapi-compiler-fortran intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic intel-oneapi-compiler-dpcpp-cpp
# install lapacke and its dependencies, both standard and the version with 64 bit integers
RUN DEBIAN_FRONTEND=noninteractive apt -y install liblapacke-dev liblapacke64-dev libopenblas-dev libopenblas-openmp-dev libopenblas64-dev libopenblas64-openmp-dev
# install MPI stack
RUN DEBIAN_FRONTEND=noninteractive apt -y install mpi-default-dev mpi-default-bin
# install packages needed to run python scripts for checks
RUN DEBIAN_FRONTEND=noninteractive apt -y install python3 python-is-python3 python3-regex
# install packages needed to run doxygen to create html docs
@@ -53,7 +55,7 @@ FROM debian:bookworm-slim AS np-tmcode-run-minimal
WORKDIR /root
# install the strictly needed runtime libraries needed to run the executables
# and the python check scripts
RUN DEBIAN_FRONTEND=noninteractive apt update && DEBIAN_FRONTEND=noninteractive apt upgrade && DEBIAN_FRONTEND=noninteractive apt -y install libgfortran5 libgcc-s1 libhdf5-103-1 libstdc++6 libssl3 libcurl4 libsz2 zlib1g libnghttp2-14 libidn2-0 librtmp1 libssh2-1 libpsl5 libgssapi-krb5-2 libldap-2.5-0 libzstd1 libbrotli1 libaec0 libunistring2 libgmp10 libkrb5-3 libk5crypto3 libcom-err2 libkrb5support0 libsasl2-2 libp11-kit0 libtasn1-6 libkeyutils1 libffi8 liblapacke64 libopenblas64-0-openmp python3 python-is-python3 python3-regex hdf5-tools && rm -rf /var/lib/apt/lists/*
RUN DEBIAN_FRONTEND=noninteractive apt update && DEBIAN_FRONTEND=noninteractive apt upgrade && DEBIAN_FRONTEND=noninteractive apt -y install libgfortran5 libgcc-s1 libhdf5-103-1 libstdc++6 libssl3 libcurl4 libsz2 zlib1g libnghttp2-14 libidn2-0 librtmp1 libssh2-1 libpsl5 libgssapi-krb5-2 libldap-2.5-0 libzstd1 libbrotli1 libaec0 libunistring2 libgmp10 libkrb5-3 libk5crypto3 libcom-err2 libkrb5support0 libsasl2-2 libp11-kit0 libtasn1-6 libkeyutils1 libffi8 liblapacke64 libopenblas64-0-openmp python3 python-is-python3 python3-regex hdf5-tools mpi-default-bin && rm -rf /var/lib/apt/lists/*
COPY --from=np-tmcode-run-dev /root /root
# remove everything which is not needed to run the codes
RUN cd /root/np-tmcode && find build -name "*.o" -exec rm -v \{\} \; && find build -name "*.gcno" -exec rm -v \{\} \; && cd src && rm -rvf cluster libnptm trapping include sphere Makefile make.inc README.md && cd .. && rm -rvf containers && cd doc && rm -rvf src && cd build/latex && rm -rvf *.tex *.out *.sty *.ind *.log *.toc *.ilg *.idx *.aux *.eps Makefile class*.pdf
+374 −207
Original line number Diff line number Diff line
@@ -12,6 +12,11 @@
#ifdef _OPENMP
#include <omp.h>
#endif
#ifdef USE_MPI
#ifndef MPI_VERSION
#include <mpi.h>
#endif
#endif

#ifndef INCLUDE_TYPES_H_
#include "../include/types.h"
@@ -53,7 +58,7 @@ using namespace std;

// I would like to put it all in a struct, but then I'd have to write a constructor for it, due to members defined as references, creating a worse nightmare than the one I'd like to simplify...

int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConfiguration *gconf, ScatteringAngles *sa, ClusterIterationData *cid, FILE *output, const string& output_path, fstream& tppoan, Logger *logger);
int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConfiguration *gconf, ScatteringAngles *sa, ClusterIterationData *cid, FILE *output, const string& output_path, fstream& tppoan);

/*! \brief C++ implementation of CLU
 *
@@ -61,14 +66,16 @@ int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConf
 *  \param data_file: `string` Name of the input data file.
 *  \param output_path: `string` Directory to write the output files in.
 */
void cluster(const string& config_file, const string& data_file, const string& output_path) {
void cluster(const string& config_file, const string& data_file, const string& output_path, const mixMPI *mpidata) {
  chrono::time_point<chrono::high_resolution_clock> t_start = chrono::high_resolution_clock::now();
  chrono::duration<double> elapsed;
  string message;
  string timing_name = output_path + "/c_timing.log";
  string timing_name = output_path + "/c_timing_mpi"+ to_string(mpidata->rank) +".log";
  FILE *timing_file = fopen(timing_name.c_str(), "w");
  Logger *time_logger = new Logger(LOG_DEBG, timing_file);
  Logger *logger = new Logger(LOG_INFO);
  // the following only happens on MPI process 0
  if (mpidata->rank == 0) {
    logger->log("INFO: making legacy configuration...", LOG_INFO);
    ScattererConfiguration *sconf = NULL;
    try {
@@ -100,7 +107,7 @@ void cluster(const string& config_file, const string& data_file, const string& o
      ScatteringAngles *p_scattering_angles = new ScatteringAngles(gconf);
      double wp = sconf->wp;
      FILE *output = fopen((output_path + "/c_OCLU").c_str(), "w");
    ClusterIterationData *cid = new ClusterIterationData(gconf, sconf);
      ClusterIterationData *cid = new ClusterIterationData(gconf, sconf, mpidata);
      const int ndi = cid->c4->nsph * cid->c4->nlim;
      np_int ndit = 2 * ndi;
      logger->log("INFO: Size of matrices to invert: " + to_string((int64_t)ndit) + " x " + to_string((int64_t)ndit) +".\n");
@@ -176,10 +183,10 @@ void cluster(const string& config_file, const string& data_file, const string& o
	// do the first iteration on jxi488 separately, since it seems to be different from the others
	int jxi488 = 1;
	chrono::time_point<chrono::high_resolution_clock> start_iter_1 = chrono::high_resolution_clock::now();
      int jer = cluster_jxi488_cycle(jxi488, sconf, gconf, p_scattering_angles, cid, output, output_path, tppoan, logger);
	int jer = cluster_jxi488_cycle(jxi488, sconf, gconf, p_scattering_angles, cid, output, output_path, tppoan);
	chrono::time_point<chrono::high_resolution_clock> end_iter_1 = chrono::high_resolution_clock::now();
	elapsed = start_iter_1 - t_start;
      message = "INFO: Calculation setup took " + to_string(elapsed.count()) + "s.\n";
	string message = "INFO: Calculation setup took " + to_string(elapsed.count()) + "s.\n";
	logger->log(message);
	time_logger->log(message);
	elapsed = end_iter_1 - start_iter_1;
@@ -187,6 +194,15 @@ void cluster(const string& config_file, const string& data_file, const string& o
	logger->log(message);
	time_logger->log(message);

	// here go the calls that send data to be duplicated on other MPI processes from process 0 to others, using MPI broadcasts, but only if MPI is actually used
#ifdef MPI_VERSION
	if (mpidata->mpirunning) {
	  gconf->mpibcast(mpidata);
	  sconf->mpibcast(mpidata);	    
	  cid->mpibcast(mpidata);
	  p_scattering_angles->mpibcast(mpidata);
	}	
#endif
	// Create this variable and initialise it with a default here, so that it is defined anyway, with or without OpenMP support enabled
	int ompnumthreads = 1;

@@ -211,9 +227,9 @@ void cluster(const string& config_file, const string& data_file, const string& o
	  } else {
	    // this is not thread 0, so do create fresh copies of all local variables
	    cid_2 = new ClusterIterationData(*cid);
	  output_2 = fopen((output_path + "/c_OCLU_" + to_string(myompthread)).c_str(), "w");
	    output_2 = fopen((output_path + "/c_OCLU_" + to_string(mpidata->rank) + "_" + to_string(myompthread)).c_str(), "w");
	    tppoanp_2 = new fstream;
	  tppoanp_2->open((output_path + "/c_TPPOAN_" + to_string(myompthread)).c_str(), ios::out | ios::binary);
	    tppoanp_2->open((output_path + "/c_TPPOAN_" + to_string(mpidata->rank) + "_" + to_string(myompthread)).c_str(), ios::out | ios::binary);
	  }
	  fstream &tppoan_2 = *tppoanp_2;
	  // make sure all threads align here: I don't want the following loop to accidentally start for thread 0, possibly modifying some variables before they are copied by all other threads
@@ -221,8 +237,8 @@ void cluster(const string& config_file, const string& data_file, const string& o
	  if (myompthread==0) logger->log("Syncing OpenMP threads and starting the loop on wavelengths\n");
	  // ok, now I can actually start the parallel calculations
#pragma omp for
	for (jxi488 = 2; jxi488 <= nxi; jxi488++) {
	  int jer = cluster_jxi488_cycle(jxi488, sconf, gconf, p_scattering_angles, cid_2, output_2, output_path, *tppoanp_2, logger);
	  for (jxi488 = cid_2->firstxi; jxi488 <= cid_2->lastxi; jxi488++) {
	    int jer = cluster_jxi488_cycle(jxi488, sconf, gconf, p_scattering_angles, cid_2, output_2, output_path, *tppoanp_2);
	  }

#pragma omp barrier
@@ -235,7 +251,7 @@ void cluster(const string& config_file, const string& data_file, const string& o
	  }
#pragma omp barrier
	  {
	  message = "INFO: Closing thread-local output files of thread " + to_string(myompthread) + " and syncing threads.\n";
	    string message = "INFO: Closing thread-local output files of thread " + to_string(myompthread) + " and syncing threads.\n";
	    logger->log(message);
	  }
	} // closes pragma omp parallel
@@ -245,11 +261,11 @@ void cluster(const string& config_file, const string& data_file, const string& o
	  // thread 0 already wrote on global files, skip it and take care of appending the others
	  for (int ri = 1; ri < ompnumthreads; ri++) {
	    // Giovanni, please add here in this loop code to reopen the temporary files, reread them and append them respectively to the global output and tppoan, before closing them
	  string partial_file_name = output_path + "/c_OCLU_" + to_string(ri);
	  message = "Copying ASCII output of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	    string partial_file_name = output_path + "/c_OCLU_" + to_string(mpidata->rank) + "_" + to_string(ri);
	    string message = "Copying ASCII output in MPI process " + to_string(mpidata->rank) + " of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	    logger->log(message, LOG_DEBG);
	    FILE *partial_output = fopen(partial_file_name.c_str(), "r");
	  char c = fgetc(partial_output);
	    int c = fgetc(partial_output);
	    while (c != EOF) {
	      fputc(c, output);
	      c = fgetc(partial_output);
@@ -257,8 +273,8 @@ void cluster(const string& config_file, const string& data_file, const string& o
	    fclose(partial_output);
	    remove(partial_file_name.c_str());
	    logger->log("done.\n", LOG_DEBG);
	  partial_file_name = output_path + "/c_TPPOAN_" + to_string(ri);
	  message = "Copying binary output of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	    partial_file_name = output_path + "/c_TPPOAN_" + to_string(mpidata->rank) + "_" + to_string(ri);
	    message = "Copying binary output in MPI process " + to_string(mpidata->rank) + " of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	    logger->log(message, LOG_DEBG);
	    fstream partial_tppoan;
	    partial_tppoan.open(partial_file_name.c_str(), ios::in | ios::binary);
@@ -274,6 +290,38 @@ void cluster(const string& config_file, const string& data_file, const string& o
	    logger->log("done.\n", LOG_DEBG);
	  }
	}
#endif
	// here go the code to append the files written in MPI processes > 0 to the ones on MPI process 0
#ifdef MPI_VERSION
	if (mpidata->mpirunning) {
	  // only go through this if MPI has been actually used
	  for (int rr=1; rr<mpidata->nprocs; rr++) {
	    // get the data from process rr
	    // how many openmp threads did process rr use?
	    int remotethreads;
	    MPI_Recv(&remotethreads, 1, MPI_INT, rr, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	    for (int ri=0; ri<remotethreads; ri++) {
	      // first get the ASCII local file
	      int c = 0;
	      MPI_Recv(&c, 1, MPI_INT, rr, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	      while (c != EOF) {
		fputc(c, output);
		MPI_Recv(&c, 1, MPI_INT, rr, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	      }
	      // now get the binary local file
	      long buffer_size = 0;
	      // get the size of the buffer
	      MPI_Recv(&buffer_size, 1, MPI_LONG, rr, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	      // allocate the bufer
	      char *binary_buffer = new char[buffer_size];
	      // actually receive the buffer
	      MPI_Recv(binary_buffer, buffer_size, MPI_CHAR, rr, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	      // we can write it to disk
	      tppoan.write(binary_buffer, buffer_size);
	      delete[] binary_buffer;
	    }
	  }
	}
#endif
	tppoanp->close();
	delete tppoanp;
@@ -293,19 +341,136 @@ void cluster(const string& config_file, const string& data_file, const string& o
    delete gconf;
    chrono::time_point<chrono::high_resolution_clock> t_end = chrono::high_resolution_clock::now();
    elapsed = t_end - t_start;
  message = "INFO: Calculation lasted " + to_string(elapsed.count()) + "s.\n";
    string message = "INFO: Calculation lasted " + to_string(elapsed.count()) + "s.\n";
    logger->log(message);
    logger->log("Finished: output written to " + output_path + "/c_OCLU\n");
    time_logger->log(message);
  }

#ifdef MPI_VERSION
  else {
    // here go the code for MPI processes other than 0
    // copy gconf, sconf, cid and p_scattering_angles from MPI process 0
    GeometryConfiguration *gconf = new GeometryConfiguration(mpidata);
    ScattererConfiguration *sconf = new ScattererConfiguration(mpidata);
    ClusterIterationData *cid = new ClusterIterationData(mpidata);
    ScatteringAngles *p_scattering_angles = new ScatteringAngles(mpidata);
    // open separate files for other MPI processes
    // File *output = fopen((output_path + "/c_OCLU_mpi"+ to_string(mpidata->rank)).c_str(), "w");
    // fstream *tppoanp = new fstream;
    // fstream &tppoan = *tppoanp;
    // string tppoan_name = output_path + "/c_TPPOAN_mpi"+ to_string(mpidata->rank);
    // tppoan.open(tppoan_name.c_str(), ios::out | ios::binary);
    // Create this variable and initialise it with a default here, so that it is defined anyway, with or without OpenMP support enabled
    int ompnumthreads = 1;

#pragma omp parallel
    {
      // Create and initialise this variable here, so that if OpenMP is enabled it is local to the thread, and if OpenMP is not enabled it has a well-defiled value anyway
      int myompthread = 0;
#ifdef _OPENMP
      // If OpenMP is enabled, give actual values to myompthread and ompnumthreads, and open thread-local output files
      myompthread = omp_get_thread_num();
      if (myompthread == 0) ompnumthreads = omp_get_num_threads();
#endif
      // To test parallelism, I will now start feeding this function with "clean" copies of the parameters, so that they will not be changed by previous iterations, and each one will behave as the first one. Define all (empty) variables here, so they have the correct scope, then they get different definitions depending on thread number
      ClusterIterationData *cid_2 = NULL;
      FILE *output_2 = NULL;
      fstream *tppoanp_2 = NULL;
      // for threads other than the 0, create distinct copies of all relevant data, while for thread 0 just define new references / pointers to the original ones
      if (myompthread == 0) {
	cid_2 = cid;
	// output_2 = output;
	// tppoanp_2 = tppoanp;
      } else {
	// this is not thread 0, so do create fresh copies of all local variables
	cid_2 = new ClusterIterationData(*cid);
      }
      output_2 = fopen((output_path + "/c_OCLU_" + to_string(mpidata->rank) + "_" + to_string(myompthread)).c_str(), "w");
      tppoanp_2 = new fstream;
      tppoanp_2->open((output_path + "/c_TPPOAN_" + to_string(mpidata->rank) + "_" + to_string(myompthread)).c_str(), ios::out | ios::binary);
      fstream &tppoan_2 = *tppoanp_2;
      // make sure all threads align here: I don't want the following loop to accidentally start for thread 0, possibly modifying some variables before they are copied by all other threads
#pragma omp barrier
      if (myompthread==0) logger->log("Syncing OpenMP threads and starting the loop on wavelengths\n");
      // ok, now I can actually start the parallel calculations
#pragma omp for
      for (int jxi488 = cid_2->firstxi; jxi488 <= cid_2->lastxi; jxi488++) {
	int jer = cluster_jxi488_cycle(jxi488, sconf, gconf, p_scattering_angles, cid_2, output_2, output_path, *tppoanp_2);
      }

#pragma omp barrier
      // only threads different from 0 have to free local copies of variables
      if (myompthread != 0) {
	delete cid_2;
      }
      fclose(output_2);
      tppoanp_2->close();
      delete tppoanp_2;
#pragma omp barrier
      {
	string message = "INFO: Closing thread-local output files of thread " + to_string(myompthread) + " and syncing threads.\n";
	logger->log(message);
      }
    } // closes pragma omp parallel
#pragma omp barrier
    {
      // tell MPI process 0 how many threads we have on this process (not necessarily the same across all processes)
      MPI_Send(&ompnumthreads, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
      // reopen local files, send them all to MPI process 0
      for (int ri = 0; ri < ompnumthreads; ri++) {
	// Giovanni, please add here in this loop code to reopen the temporary files, reread them and append them respectively to the global output and tppoan, before closing them
	string partial_file_name = output_path + "/c_OCLU_" + to_string(mpidata->rank) + "_" + to_string(ri);
	string message = "Copying ASCII output in MPI process " + to_string(mpidata->rank) + " of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	logger->log(message, LOG_DEBG);
	FILE *partial_output = fopen(partial_file_name.c_str(), "r");
	int c = 0;
	while (c != EOF) {
	  c = fgetc(partial_output);
	  MPI_Send(&c, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
	}
	fclose(partial_output);
	remove(partial_file_name.c_str());
	logger->log("done.\n", LOG_DEBG);
	partial_file_name = output_path + "/c_TPPOAN_" + to_string(mpidata->rank) + "_" + to_string(ri);
	message = "Copying binary output in MPI process " + to_string(mpidata->rank) + " of thread " + to_string(ri) + " of " + to_string(ompnumthreads - 1) + "... ";
	logger->log(message, LOG_DEBG);
	fstream partial_tppoan;
	partial_tppoan.open(partial_file_name.c_str(), ios::in | ios::binary);
	partial_tppoan.seekg(0, ios::end);
	long buffer_size = partial_tppoan.tellg();
	char *binary_buffer = new char[buffer_size];
	partial_tppoan.seekg(0, ios::beg);
	partial_tppoan.read(binary_buffer, buffer_size);
	// tell MPI process 0 how large is the buffer
	MPI_Send(&buffer_size, 1, MPI_LONG, 0, 1, MPI_COMM_WORLD);
	// actually send the buffer
	MPI_Send(binary_buffer, buffer_size, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
	// tppoan.write(binary_buffer, buffer_size);
	partial_tppoan.close();
	delete[] binary_buffer;
	remove(partial_file_name.c_str());
	logger->log("done.\n", LOG_DEBG);
      }
    }
    // Clean memory
    delete cid;
    delete p_scattering_angles;
    delete sconf;
    delete gconf;

  }
#endif
  fclose(timing_file);
  delete time_logger;
  delete logger;
}

int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConfiguration *gconf, ScatteringAngles *sa, ClusterIterationData *cid, FILE *output, const string& output_path, fstream& tppoan, Logger *logger)
int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConfiguration *gconf, ScatteringAngles *sa, ClusterIterationData *cid, FILE *output, const string& output_path, fstream& tppoan)
{
  int nxi = sconf->number_of_scales;
  string message = "INFO: running scale iteration " + to_string(jxi488) + " of " + to_string(nxi) + ".\n";
  Logger *logger = new Logger(LOG_INFO);
  logger->log(message);
  chrono::duration<double> elapsed;
  chrono::time_point<chrono::high_resolution_clock> interval_start, interval_end;
@@ -964,5 +1129,7 @@ int cluster_jxi488_cycle(int jxi488, ScattererConfiguration *sconf, GeometryConf
  
  logger->log("INFO: finished scale iteration " + to_string(jxi488) + " of " + to_string(nxi) + ".\n");

  delete logger;

  return jer;
}
+18 −2
Original line number Diff line number Diff line
@@ -29,9 +29,13 @@
#include "../include/Configuration.h"
#endif

#ifndef INCLUDE_COMMONS_H_
#include "../include/Commons.h"
#endif

using namespace std;

extern void cluster(const string& config_file, const string& data_file, const string& output_path);
extern void cluster(const string& config_file, const string& data_file, const string& output_path, const mixMPI *mpidata);

/*! \brief Main program entry point.
 *
@@ -46,6 +50,14 @@ extern void cluster(const string& config_file, const string& data_file, const st
 * \return result: `int` An exit code passed to the OS (0 for succesful execution).
 */
int main(int argc, char **argv) {
#ifdef MPI_VERSION
	int ierr = MPI_Init(&argc, &argv);
	// create and initialise class with essential MPI data
	mixMPI *mpidata = new mixMPI(MPI_COMM_WORLD);
#else
	// create a the class with dummy data if we are not using MPI at all
	mixMPI *mpidata = new mixMPI();
#endif
  	string config_file = "../../test_data/cluster/DEDFB";
	string data_file = "../../test_data/cluster/DCLU";
	string output_path = ".";
@@ -54,6 +66,10 @@ int main(int argc, char **argv) {
		data_file = string(argv[2]);
		output_path = string(argv[3]);
	}
	cluster(config_file, data_file, output_path);
	cluster(config_file, data_file, output_path, mpidata);
#ifdef MPI_VERSION
	MPI_Finalize();
#endif
	delete mpidata;
	return 0;
}
+183 −2

File changed.

Preview size limit exceeded, changes collapsed.

Loading