Skip to content
Snippets Groups Projects
MetisDistribution.hpp 13.76 KiB
/*
 * MetisDistribution.hpp
 *
 *  Created on: Nov 19, 2015
 *      Author: Antonio Leo
 */

#ifndef SRC_DECOMPOSITION_METISDISTRIBUTION_HPP_
#define SRC_DECOMPOSITION_METISDISTRIBUTION_HPP_

#include "SubdomainGraphNodes.hpp"
#include "metis_util.hpp"

#define METIS_DISTRIBUTION_ERROR_OBJECT std::runtime_error("Metis runtime error");

/*! \brief sub-domain list and weight
 *
 */
struct met_sub_w
{
	//! sub-domain id
	size_t id;

	//! sub-domain weight / assignment (it depend in which context is used)
	size_t w;

	static bool noPointers() {return true;}
};

/*! \brief Class that distribute sub-sub-domains across processors using Metis Library
 *
 * Given a graph and setting Computational cost, Communication cost (on the edge) and
 * Migration cost or total Communication costs, it produce the optimal distribution
 *
 * ### Initialize a Cartesian graph and decompose
 * \snippet Distribution_unit_tests.hpp Initialize a Metis Cartesian graph and decompose
 *
 * ### Set Computation Communication and Migration cost
 * \snippet Distribution_unit_tests.hpp Decomposition Metis with weights
 *
 */

template<unsigned int dim, typename T>
class MetisDistribution
{
	//! Vcluster
	Vcluster<> & v_cl;

	//! Structure that store the cartesian grid information
	grid_sm<dim, void> gr;

	//! rectangular domain to decompose
	Box<dim, T> domain;

	//! Global sub-sub-domain graph
	Graph_CSR<nm_v<dim>, nm_e> gp;

	//! Flag that indicate if we are doing a test (In general it fix the seed)
	bool testing = false;

	//! Metis decomposer utility
	Metis<Graph_CSR<nm_v<dim>, nm_e>> metis_graph;

	//! unordered map that map global sub-sub-domain to owned_cost_sub id
	std::unordered_map<size_t,size_t> owner_scs;

	//! list owned sub-sub-domains set for computation cost
	openfpm::vector<met_sub_w> owner_cost_sub;

	//! received assignment
	openfpm::vector<met_sub_w> recv_ass;

	/*! \brief Check that the sub-sub-domain id exist
	 *
	 * \param id sub-sub-domain id
	 *
	 */
	inline void check_overflow(size_t id)
	{
#ifdef SE_CLASS1
		if (id >= gp.getNVertex())
		{
			std::cerr << "Error " << __FILE__ ":" << __LINE__ << " such sub-sub-domain doesn't exist (id = " << id << ", " << "total size = " << gp.getNVertex() << ")\n";
			ACTION_ON_ERROR(METIS_DISTRIBUTION_ERROR_OBJECT)
		}
#endif
	}

	/*! \brief Check that the sub-sub-domain id exist
	 *
	 * \param id sub-sub-domain id
	 *
	 */
	inline void check_overflowe(size_t id, size_t e)
	{
#ifdef SE_CLASS1
		if (e >= gp.getNChilds(id))
		{
			std::cerr << "Error " << __FILE__ ":" << __LINE__ << " for the sub-sub-domain " << id << " such neighborhood doesn't exist (e = " << e << ", " << "total size = " << gp.getNChilds(id) << ")\n";
			ACTION_ON_ERROR(METIS_DISTRIBUTION_ERROR_OBJECT)
		}
#endif
	}

public:

	static constexpr unsigned int computation = nm_v_computation;

	/*! \brief constructor
	 *
	 * \param v_cl vcluster
	 *
	 */
	MetisDistribution(Vcluster<> & v_cl)
	:v_cl(v_cl),metis_graph(gp)
	{
#ifdef SE_CLASS2
			check_new(this,8,VECTOR_EVENT,1);
#endif
	}

	/*! \brief Copy constructor
	 *
	 * \param mt distribution to copy
	 *
	 */
	MetisDistribution(const MetisDistribution & mt)
	:v_cl(mt.v_cl)
	{
#ifdef SE_CLASS2
			check_valid(mt);
			check_new(this,8,VECTOR_EVENT,1);
#endif
		this->operator=(mt);
	}

	/*! \brief Copy constructor
	 *
	 * \param mt distribution to copy
	 *
	 */
	MetisDistribution(MetisDistribution && mt)
	{
#ifdef SE_CLASS2
			check_valid(mt);
			check_new(this,8,VECTOR_EVENT,1);
#endif
		this->operator=(mt);
	}

	/*! \brief Destructor
	 *
	 *
	 */
	~MetisDistribution()
	{
#ifdef SE_CLASS2
		check_delete(this);
#endif
	}


	/*! \brief create a Cartesian distribution graph
	 *
	 * \param grid grid info (sub-sub somains on each dimension)
	 * \param dom domain (domain where the sub-sub-domains are defined)
	 *
	 */
	void createCartGraph(grid_sm<dim, void> & grid, Box<dim, T> dom)
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		// NON periodic boundary conditions
		size_t bc[dim];

		for (size_t i = 0 ; i < dim ; i++)
			bc[i] = NON_PERIODIC;

		// Set grid and domain
		gr = grid;
		domain = dom;

		// Create a cartesian grid graph
		CartesianGraphFactory<dim, Graph_CSR<nm_v<dim>, nm_e>> g_factory_part;
		gp = g_factory_part.template construct<NO_EDGE, nm_v_id, T, dim - 1, 0>(gr.getSize(), domain, bc);

		// Init to 0.0 axis z (to fix in graphFactory)
		if (dim < 3)
		{
			for (size_t i = 0; i < gp.getNVertex(); i++)
			{
				gp.vertex(i).template get<nm_v_x>()[2] = 0.0;
			}
		}

		for (size_t i = 0; i < gp.getNVertex(); i++)
			gp.vertex(i).template get<nm_v_global_id>() = i;
	}

	/*! \brief Get the current graph (main)
	 *
	 * \return the current sub-sub domain Graph
	 *
	 */
	Graph_CSR<nm_v<dim>, nm_e> & getGraph()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		return gp;
	}

	/*! \brief Distribute the sub-sub-domains
	 *
	 */
	void decompose()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif

		// Gather the sub-domain weight in one processor
		recv_ass.clear();
		v_cl.SGather(owner_cost_sub,recv_ass,0);

		if (v_cl.getProcessUnitID() == 0)
		{
			if (recv_ass.size() != 0)
			{
				// we fill the assignment
				for (size_t i = 0 ; i < recv_ass.size() ; i++)
					gp.template vertex_p<nm_v_computation>(recv_ass.get(i).id) = recv_ass.get(i).w;

				metis_graph.initMetisGraph(v_cl.getProcessingUnits(),true);
			}
			else
				metis_graph.initMetisGraph(v_cl.getProcessingUnits(),false);
			metis_graph.onTest(testing);

			// decompose
			metis_graph.template decompose<nm_v_proc_id>();

			if (recv_ass.size() != 0)
			{
				// we fill the assignment
				for (size_t i = 0 ; i < recv_ass.size() ; i++)
					recv_ass.get(i).w = gp.template vertex_p<nm_v_proc_id>(recv_ass.get(i).id);
			}
			else
			{
				recv_ass.resize(gp.getNVertex());

				// we fill the assignment
				for (size_t i = 0 ; i < gp.getNVertex() ; i++)
				{
					recv_ass.get(i).id = i;
					recv_ass.get(i).w = gp.template vertex_p<nm_v_proc_id>(i);
				}
			}
		}
		else
		{
			metis_graph.inc_dec();
		}

		recv_ass.resize(gp.getNVertex());

		// broad cast the result
		v_cl.Bcast(recv_ass,0);
		v_cl.execute();
		owner_scs.clear();
		owner_cost_sub.clear();

		size_t j = 0;

		// Fill the metis graph
		for (size_t i = 0 ; i < recv_ass.size() ; i++)
		{
			gp.template vertex_p<nm_v_proc_id>(recv_ass.get(i).id) = recv_ass.get(i).w;

			if (recv_ass.get(i).w == v_cl.getProcessUnitID())
			{
				owner_scs[recv_ass.get(i).id] = j;
				j++;
				owner_cost_sub.add();
				owner_cost_sub.last().id = recv_ass.get(i).id;
				owner_cost_sub.last().w = 1;
			}
		}
	}

	/*! \brief Refine current decomposition
	 *
	 * In metis case it just re-decompose
	 *
	 */
	void refine()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif

		decompose();
	}

	/*! \brief Redecompose current decomposition
	 *
	 */
	void redecompose()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		decompose();
	}


	/*! \brief Function that return the position (point P1) of the sub-sub domain box in the space
	 *
	 * \param id vertex id
	 * \param pos vector that contain x, y, z
	 *
	 */
	void getSSDomainPos(size_t id, T (&pos)[dim])
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		check_overflow(id);

		// Copy the geometrical informations inside the pos vector
		pos[0] = gp.vertex(id).template get<nm_v_x>()[0];
		pos[1] = gp.vertex(id).template get<nm_v_x>()[1];
		if (dim == 3)
		{pos[2] = gp.vertex(id).template get<nm_v_x>()[2];}
	}

	/*! \brief function that get the computational cost of the sub-sub-domain
	 *
	 * \param id sub-sub-domain
	 *
	 * \return the comutational cost
	 *
	 */
	size_t getComputationalCost(size_t id)
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		check_overflow(id);
		return gp.vertex(id).template get<nm_v_computation>();
	}


	/*! \brief Set computation cost on a sub-sub domain
	 *
	 * \param id sub-sub domain id
	 * \param cost
	 *
	 */
	void setComputationCost(size_t id, size_t cost)
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
#ifdef SE_CLASS1
		check_overflow(id);
#endif

		auto fnd = owner_scs.find(id);
		if (fnd == owner_scs.end())
		{
			std::cerr << __FILE__ << ":" << __LINE__ << " Error you are setting a sub-sub-domain the processor does not own" << std::endl;
		}
		else
		{
			size_t id = fnd->second;
			owner_cost_sub.get(id).w = cost;
		}
	}

	/*! \brief Set migration cost on a sub-sub domain
	 *
	 * \param id of the sub-sub domain
	 * \param cost
	 */
	void setMigrationCost(size_t id, size_t cost)
	{
#ifdef SE_CLASS2
		check_valid(this,8);
#endif
#ifdef SE_CLASS1
		check_overflow(id);
#endif

		gp.vertex(id).template get<nm_v_migration>() = cost;
	}

	/*! \brief Set communication cost between neighborhood sub-sub-domains (weight on the edge)
	 *
	 * \param id sub-sub domain
	 * \param e id in the neighborhood list (id in the adjacency list)
	 * \param cost
	 */
	void setCommunicationCost(size_t id, size_t e, size_t cost)
	{
#ifdef SE_CLASS2
		check_valid(this,8);
#endif
#ifdef SE_CLASS1
		check_overflow(id);
		check_overflowe(id,e);
#endif

		gp.getChildEdge(id, e).template get<nm_e::communication>() = cost;
	}

	/*! \brief Returns total number of sub-sub-domains
	 *
	 * \return sub-sub domain numbers
	 *
	 */
	size_t getNSubSubDomains()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		return gp.getNVertex();
	}

	/*! \brief Returns total number of neighbors of one sub-sub-domain
	 *
	 * \param id of the sub-sub-domain
	 */
	size_t getNSubSubDomainNeighbors(size_t id)
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
#ifdef SE_CLASS1
		check_overflow(id);
#endif

		return gp.getNChilds(id);
	}

	/*! \brief Compute the unbalance of the processor compared to the optimal balance
	 *
	 * \warning all processor must call this function
	 *
	 * \return the unbalance from the optimal one 0.01 mean 1%
	 */
	float getUnbalance()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		size_t load_p = getProcessorLoad();

		float load_avg = load_p;
		v_cl.sum(load_avg);
		v_cl.execute();

		if (load_avg == 0)
		{
			// count the number if sub-sub-domain assigned
			load_avg = owner_cost_sub.size();

			v_cl.sum(load_avg);
			v_cl.execute();
		}

		load_avg /= v_cl.getProcessingUnits();

		return ((float)load_p - load_avg) / load_avg;
	}

	/*! \brief Return the total number of sub-sub-domains in the distribution graph
	 *
	 * \return the total number of sub-sub-domains set
	 *
	 */
	size_t getNOwnerSubSubDomains() const
	{
		return owner_cost_sub.size();
	}

	/*! \brief Return the id of the set sub-sub-domain
	 *
	 * \param id id in the list of the set sub-sub-domains
	 *
	 * \return the id
	 *
	 */
	size_t getOwnerSubSubDomain(size_t id) const
	{
		return owner_cost_sub.get(id).id;
	}

	/*! \brief It set the Classs on test mode
	 *
	 * At the moment it fix the seed to have reproducible results
	 *
	 */
	void onTest()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		testing = true;
	}

	/*! \brief Write the distribution graph into file
	 *
	 * \param out output filename
	 *
	 */
	void write(std::string out)
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif

		VTKWriter<Graph_CSR<nm_v<dim>, nm_e>, VTK_GRAPH> gv2(gp);
		gv2.write(std::to_string(v_cl.getProcessUnitID()) + "_" + out + ".vtk");

	}

	/*! \brief Compute the processor load
	 *
	 * \warning all processors must call this function
	 *
	 * \return the total computation cost
	 */
	size_t getProcessorLoad()
	{
#ifdef SE_CLASS2
			check_valid(this,8);
#endif
		openfpm::vector<size_t> loads(v_cl.getProcessingUnits());

		size_t load = 0;

		if (v_cl.getProcessUnitID() == 0)
		{
			for (size_t i = 0; i < gp.getNVertex(); i++)
			{loads.get(gp.template vertex_p<nm_v_proc_id>(i)) += gp.template vertex_p<nm_v_computation>(i);}

			for (size_t i = 0 ; i < v_cl.getProcessingUnits() ; i++)
			{
				v_cl.send(i,1234,&loads.get(i),sizeof(size_t));
			}
		}
		v_cl.recv(0,1234,&load,sizeof(size_t));
		v_cl.execute();

		return load;
	}
	/*! \brief operator=
	 *
	 * \param mt object to copy
	 *
	 * \return itself
	 *
	 */
	MetisDistribution & operator=(const MetisDistribution & mt)
	{
#ifdef SE_CLASS2
			check_valid(&mt,8);
			check_valid(this,8);
#endif
		this->gr = mt.gr;
		this->domain = mt.domain;
		this->gp = mt.gp;
		this->owner_cost_sub = mt.owner_cost_sub;
		this->owner_scs = mt.owner_scs;
		return *this;
	}

	/*! \brief operator=
	 *
	 * \param mt object to copy
	 *
	 * \return itself
	 *
	 */
	MetisDistribution & operator=(MetisDistribution && mt)
	{
#ifdef SE_CLASS2
			check_valid(mt);
			check_valid(this,8);
#endif
		this->gr = mt.gr;
		this->domain = mt.domain;
		this->gp.swap(mt.gp);
		this->owner_cost_sub.swap(mt.owner_cost_sub);
		this->owner_scs.swap(mt.owner_scs);
		return *this;
	}

	/*! \brief operator==
	 *
	 * \param mt Metis distribution to compare with
	 *
	 * \return true if the distribution match
	 *
	 */
	inline bool operator==(const MetisDistribution & mt)
	{
#ifdef SE_CLASS2
			check_valid(&mt,8);
			check_valid(this,8);
#endif
		bool ret = true;

		ret &= (this->gr == mt.gr);
		ret &= (this->domain == mt.domain);
		ret &= (this->gp == mt.gp);

		return ret;
	}

	/*! \brief Set the tolerance for each partition
	 *
	 * \param tol tolerance
	 *
	 */
	void setDistTol(double tol)
	{
		metis_graph.setDistTol(tol);
	}



	/*! \brief function that get the weight of the vertex
	 *
	 * \param id vertex id
	 *
	 */
	size_t getSubSubDomainComputationCost(size_t id)
	{
#ifdef SE_CLASS1
		if (id >= gp.getNVertex())
			std::cerr << __FILE__ << ":" << __LINE__ << "Such vertex doesn't exist (id = " << id << ", " << "total size = " << gp.getNVertex() << ")\n";
#endif

		auto fnd = owner_scs.find(id);
		if (fnd == owner_scs.end())
		{
			std::cerr << __FILE__ << ":" << __LINE__ << " Error you are setting a sub-sub-domain that the processor does not own" << std::endl;
			return 0;
		}

		size_t ids = fnd->second;
		return owner_cost_sub.get(ids).w;
	}

	/*! \brief Get the decomposition counter
	 *
	 * \return the decomposition counter
	 *
	 */
	size_t get_ndec()
	{
		return metis_graph.get_ndec();
	}
};

#endif /* SRC_DECOMPOSITION_METISDISTRIBUTION_HPP_ */