/* This file is part of the EZLib Library Copyright (C) 2026 Jean-Michel RICHER This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // ================================================================== // Author: Jean-Michel Richer // Email: jean-michel.richer@univ-angers.fr // Date: Aug 2020 // Last modified: September 2026 // Purpose: interface and class to facilitate use of MPI // ================================================================== #pragma once #include #include #include #include #include #include #include #include #include using namespace std; #include /** * @brief EZ MPI is a wrapper for MPI C++. It simplifies the use * of MPI send, receive, gather, scatter functions. * For the gather, scatter and reduce functions the processor * of rank 0 is considered as the "master" that collects or * sends data. */ namespace ez { namespace mpi { enum { MASTER = 0, SLAVE_1, SLAVE_2, SLAVE_3, SLAVE_4 }; /** * @class Process * @brief This is the main class that defines a process. * This class gathers information about the number of other processes * working with this process and the rank of the process also called * id here. * It enables to perform operations like send and receive but also more * complex ones as gather, scatter, broadcast or reduce which are supposed * to be initiated by the master process which as the default rank of 0. */ class Process { protected: // number of process working with this one int m_nbr_processes; // identifier of current process, called rank for MPI int m_id; // identifier of remote process for to communicate with (send, receive, ...) int m_remote; // status of last operation MPI::Status m_status; // message tag if needed (default is 0) int m_message_tag; // name of process string m_name; // Linux process identifier (PID) int m_pid; // verbose mode bool m_verbose_flag; // verbose mode bool m_log_flag; // main output stream for current processor ostringstream log_stream; // temporary output stream ostringstream tmp_log; // MPI::Finalize already called ? bool finalize_already_called; private: /** * Find processor name */ void find_cpu_name() { char name[MPI::MAX_PROCESSOR_NAME]; int length; memset(name, 0, MPI::MAX_PROCESSOR_NAME); MPI::Get_processor_name(name, length); m_name = name; } /** * record output of oss into general output */ // void append(); /** * initialize nbr_process, cpu_rank, processus id */ void init() { m_nbr_processes = MPI::COMM_WORLD.Get_size(); m_id = MPI::COMM_WORLD.Get_rank(); find_cpu_name(); m_pid = getpid(); if (m_verbose_flag) { tmp_log << "pid=" << m_pid << ", id=" << m_id << endl; flush(); } } const std::string current_date_and_time() { time_t now = time(0); struct tm tstruct; char buf[80]; tstruct = *localtime(&now); // strftime(buf, sizeof(buf), "%Y-%m-%d.%X [%s]", &tstruct); strftime(buf, sizeof(buf), "%X", &tstruct); return buf; } void flush() { string str = current_date_and_time(); if (m_log_flag) { log_stream << str << " cpu " << m_id << "/" << m_nbr_processes << ": " << tmp_log.str(); } if (m_verbose_flag) { cerr << str << " cpu " << m_id << "/" << m_nbr_processes << ": " << tmp_log.str(); } tmp_log.str(""); } void print(char v) { tmp_log << v; } void print(int v) { tmp_log << v; } void print(string v) { tmp_log << v; } void print(float v) { tmp_log << v; } void print(double v) { tmp_log << v; } public: /** * Default constructor * @param argc command line number of arguments * @param argv command line arguments * @param verbose allow verbose mode * @param log allow logging mode */ Process(int argc, char* argv[], bool verbose = true, bool log = true) { m_remote = 0; m_message_tag = 0; m_verbose_flag = verbose; m_log_flag = log; finalize_already_called = false; MPI::Init(argc, argv); init(); } /** * Free resources of MPI::Init */ void finalize() { if (!finalize_already_called) { finalize_already_called = true; MPI::COMM_WORLD.Barrier(); MPI::Finalize(); } } ~Process() { if (!finalize_already_called) { finalize_already_called = true; MPI::COMM_WORLD.Barrier(); MPI::Finalize(); } } /** * set verbose mode * @param mode true to enable, false to disable */ void verbose(bool mode) { m_verbose_flag = mode; } /** * set log mode * @param mode true to enable, false to disable */ void log(bool mode) { m_log_flag = mode; } /** * Get Unix process identifier * @return unix process identifier */ int unix_pid() { return m_pid; } /** * Get processor identifier or rank * @return rank of process */ int rank() { return m_id; } /** * Get processor identifier or rank * @return rank of process * @note this method is identical to rank() */ int id() { return m_id; } /** * Get number of processors used * @return number of other processes working with this process */ int nbr_processes() { return m_nbr_processes; } /** * Get processor name * @return name of processor on which this process is executed */ string name() { return m_name; } /** * set remote processor identifier * @param rmt_id remote process identifier */ void remote(int rmt_id) { m_remote = rmt_id; } /** * set message tag * @param tag must be an integer between 0 and 32767 */ void tag(int tag) { m_message_tag = tag; } /** * Return true if this processor is the processor of rank 0 * considered as the master. */ bool is_master() { return (m_id == ez::mpi::MASTER); } bool is_slave() { return (m_id != ez::mpi::MASTER); } bool is_slave(int n) { return (m_id == n); } /** * synchronize */ void synchronize() { MPI::COMM_WORLD.Barrier(); } /** * Determine type of data T and convert it into MPI::Datatype. * This function needs to be extended with other types. */ template MPI::Datatype get_type() { if (typeid(T) == typeid(char)) { return MPI::CHAR; } else if (typeid(T) == typeid(int8_t)) { return MPI::CHAR; } else if (typeid(T) == typeid(uint8_t)) { return MPI::CHAR; } else if (typeid(T) == typeid(int)) { return MPI::INT; } else if (typeid(T) == typeid(float)) { return MPI::FLOAT; } else if (typeid(T) == typeid(double)) { return MPI::DOUBLE; } std::stringstream oss; oss << "!!!!! unknown " << typeid(T).name() << endl; throw std::runtime_error(oss.str()); return MPI::INT; } /** * Determine type of data T and convert it into a string */ template std::string get_type_name() { if (typeid(T) == typeid(char)) { return "char"; } else if (typeid(T) == typeid(int8_t)) { return "char"; } else if (typeid(T) == typeid(uint8_t)) { return "char"; } else if (typeid(T) == typeid(int)) { return "int"; } else if (typeid(T) == typeid(float)) { return "float"; } else if (typeid(T) == typeid(double)) { return "double"; } std::stringstream oss; oss << "!!!!! unknown " << typeid(T).name() << endl; throw std::runtime_error(oss.str()); return "char"; } /** * Send one instance of data to remote_cpu * @param v data to send */ template void send(T& v) { MPI::Datatype data_type = get_type(); if (m_log_flag) { tmp_log << "- send value=" << v << " of type " << get_type_name(); tmp_log << " to process=" << m_remote << endl; flush(); } MPI::COMM_WORLD.Send(&v, 1, data_type, m_remote, m_message_tag); } /** * Send an array to remote_cpu * @param arr address of the array * @param size number of elements to send */ template void send(T* arr, int size, int begin = 0, int end = -1) { MPI::Datatype data_type = get_type(); assert((begin >= 0) and (begin < size)); if (end < 0) end = size; assert((end >= 0) and (end <= size)); size = end - begin; if (m_log_flag) { tmp_log << "- send array of type " << get_type_name(); tmp_log << " from index " << begin << " to " << end; tmp_log << " of size " << size << " to process=" << m_remote << endl; flush(); } MPI::COMM_WORLD.Send(&arr[begin], size, data_type, m_remote, m_message_tag); } /** * Send data from size data of the vector starting at * given index * @param v vector * @param size number of elements to send * @param index where to start, default is first * element of the vector * */ template void send(vector& v, int begin = 0, int end = -1) { int size = static_cast(v.size()); assert((begin >= 0) and (begin < size)); if (end < 0) end = size; assert((end >= 0) and (end <= size)); size = end - begin; MPI::Datatype data_type = get_type(); MPI::COMM_WORLD.Send(&size, 1, MPI::INT, m_remote, m_message_tag); T* ptr = v.data(); MPI::COMM_WORLD.Send(&ptr[begin], size, data_type, m_remote, m_message_tag); if (m_log_flag) { tmp_log << "- send " << size << " elements of vector"; tmp_log << " of type " << get_type_name(); tmp_log << " from index " << begin << " to " << end; tmp_log << " of size " << size << " to process=" << m_remote << endl; flush(); } } /** * Receive one instance of data from remote cpu * @param v data to receive */ template void recv(T& v) { MPI::Datatype data_type = get_type(); MPI::COMM_WORLD.Recv( &v, 1, data_type, m_remote, (m_message_tag == 0) ? MPI::ANY_TAG : m_message_tag, m_status); if (m_log_flag) { tmp_log << "- receive value=" << v << " of type " << get_type_name(); tmp_log << " from process=" << m_remote << endl; flush(); } } /** * Receive an array of given size * @param arr pointer to address of the array * @param size number of elements */ template void recv(T* arr, int size) { MPI::Datatype data_type = get_type(); MPI::COMM_WORLD.Recv( &arr[0], size, data_type, m_remote, (m_message_tag == 0) ? MPI::ANY_TAG : m_message_tag, m_status); if (m_log_flag) { tmp_log << "- receive array of type " << get_type_name(); tmp_log << " of size=" << size << " from process=" << m_remote << endl; flush(); } } template void recv(vector& v) { MPI::Datatype data_type = get_type(); int size = 0; MPI::COMM_WORLD.Recv( &size, 1, MPI::INT, m_remote, (m_message_tag == 0) ? MPI::ANY_TAG : m_message_tag, m_status); v.resize(size); T* ptr = v.data(); MPI::COMM_WORLD.Recv( ptr, size, data_type, m_remote, (m_message_tag == 0) ? MPI::ANY_TAG : m_message_tag, m_status); if (m_log_flag) { tmp_log << "- receive " << size << " elements of vector"; tmp_log << " of type " << get_type_name(); tmp_log << " from process=" << m_remote << endl; flush(); } } /** * Send array and receive value in return, this is an instance * of the Sendrecv function. * @param arr address of the array to send * @param size size of the array to send * @param value value to receive */ template void sendrecv(T* array, int size, U& value) { MPI::Datatype array_data_type = get_type(); MPI::Datatype value_data_type = get_type(); if (m_log_flag) { tmp_log << "- sendrecv/send array of type " << get_type_name(); tmp_log << " of size=" << size << endl; flush(); } MPI::COMM_WORLD.Sendrecv(&array[0], size, array_data_type, m_remote, 0, &value, 1, value_data_type, MPI::ANY_SOURCE, MPI::ANY_TAG, m_status); if (m_log_flag) { tmp_log << "sendrecv/receive value=" << value << endl; flush(); } } /** * Perform reduction * @param item local data used to perform reduction * @param total global data that will contain result * @param op operation to perform (MPI::SUM, MPI::MAX, ...) */ template void reduce(T& item, T& total, const MPI::Op& op, int root_id = MASTER) { MPI::Datatype data_type = get_type(); assert((root_id >= 0) and (root_id < m_nbr_processes)); MPI::COMM_WORLD.Reduce(&item, &total, 1, data_type, op, root_id); if (m_log_flag) { tmp_log << "- reduction gives value=" << total << endl; flush(); } } /** * Perform gather operation * @param lcl_array local array that is send to master process * @param glb_array global array that will contain all local arrays */ template void gather(T* lcl_array, int size, T* glb_array, int root_id = MASTER) { MPI::Datatype data_type = get_type(); assert((root_id >= 0) and (root_id < m_nbr_processes)); MPI::COMM_WORLD.Gather(lcl_array, size, data_type, glb_array, size, data_type, root_id); if (m_log_flag) { tmp_log << "- gather" << endl; flush(); } } /** * Perform scatter operation * @param big_array array of data that will be send by to all processors * by parts * @param big_size size of the array that will be sent * @param small_array local array of data */ template void scatter(T* big_array, int big_size, T* small_array) { MPI::Datatype data_type = get_type(); int small_size = big_size / m_nbr_processes; MPI::COMM_WORLD.Scatter(big_array, small_size, data_type, small_array, small_size, data_type, 0); if (m_log_flag) { tmp_log << "- scatter array of size " << big_size; tmp_log << " into " << m_nbr_processes << " chunks of " << small_size; tmp_log << " elements" << endl; flush(); } } /** * Perform scatter operation * @param big_vec vector of data that will be send by to all processors * by parts * @param local_vec local vector of data */ template void scatter(vector& big_vec, int big_size, vector& local_vec, int begin = 0, int end = -1) { MPI::Datatype data_type = get_type(); assert((begin >= 0) and (begin < big_size)); if (end == -1) end = big_size; assert((begin < end) and (end <= big_size)); big_size = (end - begin + 1); int local_vec_size = big_size / m_nbr_processes; MPI::COMM_WORLD.Scatter(big_vec.data(), local_vec_size, data_type, local_vec.data(), local_vec_size, data_type, 0); if (m_log_flag) { tmp_log << "- scatter vector of size " << big_size; tmp_log << " into " << m_nbr_processes << " chunks of " << local_vec_size; tmp_log << " elements" << endl; flush(); } } // MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, // MPI_Comm comm) template void broadcast(T* glb_array, int size, int root_id = MASTER) { MPI::Datatype data_type = get_type(); assert((root_id >= 0) and (root_id < m_nbr_processes)); MPI::COMM_WORLD.Bcast((void*)glb_array, size, data_type, root_id); if (m_log_flag) { tmp_log << "- broadcast array of size " << size << endl; flush(); } } template void broadcast(std::vector& vec, int root_id = MASTER) { MPI::Datatype data_type = get_type(); assert((root_id >= 0) and (root_id < m_nbr_processes)); MPI::COMM_WORLD.Bcast((void*)vec.data(), static_cast(vec.size()), data_type, root_id); if (m_log_flag) { tmp_log << "- broadcast array of size " << vec.size() << " elements"; tmp_log << " from process=" << root_id << endl; flush(); } } typedef std::ostream& (*ManipFn)(std::ostream&); typedef std::ios_base& (*FlagsFn)(std::ios_base&); template Process& operator<<(vector& v) { tmp_log << '['; if (v.size() > 0) { tmp_log << v[0]; for (int i = 1; i < static_cast(v.size()); ++i) { tmp_log << ' ' << v[i]; } } tmp_log << ']'; return *this; } template // int, double, strings, etc Process& operator<<(const T& output) { tmp_log << output; return *this; } // endl, flush, setw, setfill, etc. Process& operator<<(ManipFn manip) { manip(tmp_log); if (manip == static_cast(std::flush) || manip == static_cast(std::endl)) { this->flush(); } return *this; } // setiosflags, resetiosflags Process& operator<<(FlagsFn manip) { manip(tmp_log); return *this; } void logs(ostream& out) { MPI::COMM_WORLD.Barrier(); m_verbose_flag = m_log_flag = false; if (m_id == 0) { out.flush(); out << std::endl; out << "=====================" << std::endl; out << "=== LOGGING ===" << std::endl; out << "=====================" << std::endl; out << "---------------------" << std::endl; out << "CPU " << m_id << " (pid=" << unix_pid() << ")" << std::endl; out << "---------------------" << std::endl; out << log_stream.str(); out.flush(); remote(1); int token = -255; send(token); } else { remote(m_id - 1); int token; recv(token); out << "---------------------" << std::endl; out << "CPU " << m_id << " (pid=" << unix_pid() << ")" << std::endl; out << "---------------------" << std::endl; out << log_stream.str(); out.flush(); if (m_id < m_nbr_processes - 1) { remote(m_id + 1); token = -255; send(token); } } } typedef void (*Code)(Process& p); void run(Code code) { code(*this); } }; } // end of namespace mpi } // end of namespace ez