https://hal.archives-ouvertes.fr/hal-03445891
dilworthdecomposition.cpp
/***************************************************************************
* Copyright (C) 2007 by Sid Touati *
* Sid-nospamDOTTouati-nospam@inria.fr *
* Copyright INRIA and University of Versailles (France) *
* 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 2 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, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
/*! \file dilworthdecomposition.cpp
\brief This file contains the C++ implémentation of Dilworth decomposition using the LEDA graph library.
*/
#include "dilworthdecomposition.h"
/*! \mainpage Dilworth Decomposition in C++ using the LEDA Graph library
\author : Sid Touati (in memory of Vincent Bouchitté)
\section VB Who was Vincent Bouchitté ?
Vincent Bouchitté was one of my former professors at Ecole Normale Supérieure de Lyon (France). He taught me deep and excellent fundamental results of graph theory, lattices and orders. Thanks to his courses, we were able to produce fundamental results on code optimisation published in the following article:
Sid Touati. Register Saturation in Instruction Level Parallelism. International Journal of Parallel Programming, Springer-Verlag, Volume 33, Issue 4, August 2005. 57 pages.
Vincent Bouchitté was member of the LIP laboratory at Ecole Normale Supérieure de Lyon. He died after a long disease. He was 47 years old. Very discrete, he was very appreciated by all his colleagues and students. He leaves major results in graph theory and
advised beautiful Ph.D. theses. He was buried at Salindre in France, his birthplace, on March 15th, 2005.
In memory of him, I implemented Dilworth decomposition. Vincent Bouchitté taught us beautiful algorithms and formal proofs on the subject.
\section background Quick Recall of Basic Notions and Notations on Graphs and Orders
An order is simply a directed acyclic graph (DAG). Given a DAG
\f$ G=(V,E) \f$, the following notations were used by Vincent Bouchitté and are usually used in lattices and orders algebra:
- \f$ \Gamma^+_G(u)=\{v \in V| (u,v) \in E\} \f$ is the set of successors of \f$ u \f$ in the graph \f$ G \f$ ;
- \f$ \Gamma^-_G(u)=\{v \in V| (v,u) \in E\} \f$ is the set of predecessors of \f$ u \f$ in the graph \f$ G \f$ ;
- \f$ \forall e=(u,v) \in E, source(e)=u \wedge target(e)=v \f$ . \f$ u,v \f$ are called \e endpoints ;
- \f$ \forall u,v \in V:\ u < v\Longleftrightarrow \exists \f$ a path \f$ (u,\ldots,v) \f$ in \f$ G \f$ ;
- \f$ \forall u,v \in V:\ u || v \Longleftrightarrow \neg(u<v) \wedge \neg (v<u)\f$ . \f$ u \f$ and \f$ v \f$ are said to be \e parallel ;
- \f$ \forall u \in V\ \uparrow u=\{v \in V| v=u \vee v < u \} \f$ is the set of \f$ u\f$ 's ascendants including \f$ u \f$ . In other terms, a node \f$ u \f$ is an ascendant of a node \f$ v\f$ iff \f$ u=v\f$ or if there exists a path from \f$ u\f$ to \f$ v \f$ ;
- \f$ \forall u \in V\ \downarrow u=\{v \in V| v=u \vee u < v \}\f$ is the set of \f$ u \f$ 's descendants including \f$ u \f$ . In other terms, a node \f$ u\f$ is a descendant of a node \f$ v \f$ iff \f$ u=v \f$ or if there exists a path from \f$ v \f$ to \f$ u \f$ ;
- Two edges \f$ e, e' \f$ are \e adjacent iff they share an endpoint;
- \f$ A\subseteq V \f$ is an antichain iff all nodes belonging to \f$ A \f$ are parallel. Formally, \f$ A\subseteq V \f$ is an antichain in \f$ G\f$ iff \f$ \forall u,v\in A,\ u||v \f$ ;
- \f$ AM \f$ is a \e maximal antichain iff its size in terms of number of nodes is maximal. Formally, \f$ AM \f$ is a \e maximal antichain \f$ \forall A \textrm{ antichain in } G,\quad |A|\le |AM| \f$ ;
- The size of a maximal antichain is called the \e width of the DAG and is noted \f$ w(G) \f$.
- \f$ C\subseteq V \f$ is a chain iff all nodes belonging to \f$ C \f$ are not parallel. Simply, all nodes of a chain belongs to the same path in the DAG. Formally, \f$ C\subseteq V \f$ is a chain in \f$ G\f$ iff \f$ \forall u,v\in C,\ u<v \vee v<u \f$ ;
- \f$ CD= \{C_1, \cdots, C_p\} \f$ is a chain partition of \f$ G \f$ if any \f$ C_i \in CD \f$ is a chain and: \f$ \forall u \in V, \exists !i \in [1,p]: u \in C_i \f$.
- A chain decomposition \f$ CD \f$ is minimal if its indice \f$ p \f$ is minimal. Such minimal indice is noted \f$ p(G) \f$.
- In 1950, Dilworth proved that \f$ p(G)=w(G) \f$, and each maximal antichain is equivalent to a minimal chain decomposition (and vice-versa).
\section leda Why do we choose LEDA Graph Library ?
LEDA is a famous C++ graphs and general data structures library. We have used it since many years, and we can safely say that it is \a better than other existing C++ graph and data structures libraries that we experimented (BOOST, STL, etc.). Initially, LEDA was an academic project from Germany. LEDA sources was distributed for free under a specific academic license untill version 4.2 (with g++2.95). Then, in 2001 (when g++ changed to version 3), LEDA team changed the free academic license into a low-priced academic license. LEDA is a high level library greatly helping to implement complex algorithms in a quick, robust and modular way. According to our deep experience, a C++ code using LEDA looks like a high level algorithm, allowing to easily debug it without suffering from programming details. Furthermore, LEDA offers the largest set of implementation of well known algorithms in graph theory and data structures.
\section ex Code Example for Usage
\code
int main(int argc, char *argv[])
{
graph G;
LEDA::string filename;
set<node> MA; //maximal antichain
node_array<int> C; //indices of chains
node_list nl;
h_array<int,node_list*> chain(nil);
int i,status;
int size_ma, // size of a maximal antichain
size_mc; // size of a minimal chain decomposition
node u;
if(argc!=2){
cerr << argv[0] << ": Dilworth decomposition." << endl;
cerr << "Usage:"<<argv[0] << " graph_filename" << endl;
cerr << "The filename extension should be .gw for a graph in leda/gw format, or in .gml for a graph in GML format."<< endl;
return EXIT_FAILURE;
}
filename=LEDA::string(argv[1]);
if (filename.contains(LEDA::string(".gw"))) {
cout<<"Reading GW" <<filename<<endl;
status=G.read(filename);
}
else if (filename.contains(LEDA::string(".gml"))) {
cout<<"Reading GML "<< filename<<endl;
status=G.read_gml(filename);
}
else {
cerr << "Usage:"<<argv[0] << " graph_filename" << endl;
cerr << "The filename extension should be .gw for a graph in leda/gw format, or in .gml for a graph in GML format."<< endl;
return EXIT_FAILURE;
}
switch(status){
case 0:
case 2: break;
case 1: cerr<< filename << " does not exist."<<endl; break;
case 3: cerr<<filename <<" does not contain a graph"<<endl; break;
default: return EXIT_FAILURE;
}
size_mc=MINIMAL_CHAIN(G, C);
cout<<"Minimal Chain Decomposition"<<endl;
cout<<"---------------------------"<<endl;
cout<<"There are "<<size_mc<<" chains"<<endl;
forall_nodes(u,G){
if ((chain[C[u]])==nil){
chain[C[u]]=new node_list;
}
(chain[C[u]])->append(u);
}
for(i=0;i<size_mc;i++){
cout<<"chain "<<i<<": ";
forall(u, *chain[i]){
G.print_node(u);
}
cout<<endl;
}
size_ma=MAXIMAL_ANTI_CHAIN(G, MA);
cout<<"Maximal Antichain"<<endl;
cout<<"---------------------------"<<endl;
cout<<"Size of this maximal anctichain : "<<size_ma<<" nodes"<<endl;
cout<<"Here are all these nodes:"<<endl;
i=0;
forall(u, MA){
cout<<"node "<<i<<": ";
G.print_node(u);
cout<<endl;
i++;
}
return EXIT_SUCCESS;
}
\endcode
*/
/** \fn template<class T> set<T> list_to_set(const list<T> l)
* \brief This function returns the set of members in a list. I.e, it converts an ordered list to a set.
* \param[in] l The ordered list to convert.
* \returns the set of the elements of the ordered list.
*/
template<class T> set<T> list_to_set(const list<T> l){
T m;
set<T> tmp;
forall(m , l){tmp.insert(m);}
return tmp;
}
/** \fn template<class T> list<T> set_to_list(const set<T> l)
* \brief This function returns the an ordered from a set.
* \param[in] l The set to convert.
* \returns the ordered list of the input set members.
*/
template<class T> list<T> set_to_list(const set<T> l){
T m;
list<T> tmp;
forall(m , l){tmp.append(m);}
return tmp;
}
/*! \fn int MAXIMAL_ANTI_CHAIN(const graph &G, set<node>&);
* \brief This function computes a maximal antichain chain of a DAG (order)
* \param[in] G The DAG.
* \param[out] MA A Maximal antichain.
* \returns \f$ w(G) \f$ the width of the DAG. It is equal to the size of a maximal antichain.
* \remarks
* A maximal antichain may not be unique. This function returns an arbitrary one.
* \pre G is acyclic.
*/
int MAXIMAL_ANTI_CHAIN(const graph & G, set<node>& MA){
node z,y,u,v, zi, zk, xk, xj, zj, yj, yk, yi;
edge e;
d_array<int, list<node> > C;
list<edge> card_match;
GRAPH<node,edge> G_c; /* Transitive closure of G */
graph B; /* bipartie graph */
node_array<node> Y(G, NULL), Z(G, NULL);
set<node> set_Z, set_Y, unsaturated, S, T, Tp;
list<node> Ci;
list<edge> le;
list_item item;
set<node> down;
node_array<set<node> > G_down(G); /* we associate to each node all its descendants */
node_array<int> chain(G);
node_array<node> Z_inverse, couple;
int max_AM=0, i; /* maximal antichain size */
/* if this graph is not acyclic ==> return -1 */
if (!Is_Acyclic(G, le)) {
cerr<<"Dilworth decomposition cannot be done on cyclic graph"<<endl;
return -1;
}
if (G.empty()) {
return 0;
}
/* we first begin by building transitive closure to compute descendants for each node */
G_c=TRANSITIVE_CLOSURE(G);
forall_edges(e, G_c){
u=G_c.source(e);
v=G_c.target(e);
if(u!=v) G_down[G_c[u]].insert(G_c[v]);
}
/* we now construct the bipartie graph B of G
B(G)=(Y,Z, F) such that
- Y and Z are two copies of V
- (y_i,z_i) in F iff (u_i, u_j) in E that is y_i
(resp z_i) represents x_i in Y (resp z_i in Z) */
forall_nodes(u, G){
set_Y.insert(Y[u]=B.new_node());
set_Z.insert(Z[u]=B.new_node());
}
Z_inverse.init(B,NULL);
/* Z_inverse gives u assigned to z */
forall_nodes(u,G){
Z_inverse[Z[u]]=u;
}
/* connecte y_i to each zj iff there is a path from xi to xj*/
forall_nodes(u, G){
down=G_down[u];
forall(v, down){
// if(u!=v)
B.new_edge(Y[u], Z[v]);
}
}
couple.init(B, NULL);
/* we search for the maximal cardinality matching of B
because it corresponds to a minimum chain partitions */
card_match=MAX_CARD_BIPARTITE_MATCHING(B, set_to_list<node> (set_Y), set_to_list<node>(set_Z));
forall(e, card_match){
yi=B.source(e);
zi=B.target(e);
couple[yi]=zi;
couple[zi]=yi;
}
/* antichain max = number of minimal chains */
/* now we construct the minimal chain partition */
forall(zj, set_Z){
if (couple[zj]==NULL){ /* we have a source of a new chain */
unsaturated.insert(Z_inverse[zj]);
max_AM++;
Ci.clear();
Ci.append(xj=Z_inverse[zj]);
chain[xj]=max_AM;
yj=Y[xj];
while(couple[yj]!=NULL){
zk=couple[yj];
xk=Z_inverse [zk];
Ci.append(xk);
chain[xk]=max_AM;
yj=Y[xk];
}
C[max_AM]=Ci;
}
}
/* now we have the minimal partition in C
to compte a maximal antichain, we select an element from
each chain such that it is parallel with the others */
S = unsaturated;
do {
T.clear();
/*T is the set of nodes to be changed */
forall(u, S){
down=G_down[u];
forall(v, S){
if((u!=v) && (down.member(v))){ /* u should be removed from the max antichain */
T.insert(u);
break;
}
}
}
/* Tp are nodes to be added */
Tp.clear();
forall(u, T){
Ci=C[chain[u]];
if((item=Ci.succ(Ci.search(u)))!=NULL)
Tp.insert(Ci[item]);
}
/* try now with this */
S=S-T+Tp;
} while(!T.empty());
/* the maximal antichain is S */
MA =S;
return max_AM;
}
/*! \fn int MINIMAL_CHAIN(const graph &G, node_array<int>& chain);
* \brief This function computes a minimal chain decomposition of a DAG (an order).
* \param[in] G The DAG.
* \param[out] chain \f$ \forall u \in V, chain[u] \in [0, p(G)-1]\f$ contains the number of the chain to which \f$ u \f$ belongs.
* \returns \f$ p(G) \f$ the minimal number of chains of the DAG. Dilworth proved that \f$ p(G)=w(G)\f$ , that is, it is equal to the size of a maximal antichain.
* \remarks
* A minimal chain decomposition may not be unique. This function returns an arbitrary one.
* \pre G is acyclic.
*/
int MINIMAL_CHAIN(const graph &G, node_array<int>& chain){
node z,y,u,v, zi, zk, xk, xj, zj, yj, yk, yi;
edge e;
list<edge> card_match;
GRAPH<node,edge> G_c; /* Transitive closure of G */
graph B; /* bipartie graph */
node_array<node> Y(G, NULL), Z(G, NULL);
set<node> set_Z, set_Y, unsaturated, S, T, Tp;
list<node> l_Y, l_Z;
list<node> Ci;
list<edge> le;
list_item item;
set<node> down;
node_array<set<node> > G_down(G); /* we associate to each node all its descendants */
node_array<node> Z_inverse, couple;
int i=0; /* number of chains */
chain.init(G);
/* if this graph is not acyclic ==> return -1 */
if (!Is_Acyclic(G, le)) {
cerr<<"Dilworth decomposition cannot be done on cyclic graph"<<endl;
return -1;
}
if (G.empty()) {
return 0;
}
/* we first begin by building transitive closure to compute descendants for each node */
G_c=TRANSITIVE_CLOSURE(G);
forall_edges(e, G_c){
u=G_c.source(e);
v=G_c.target(e);
if(u!=v) G_down[G_c[u]].insert(G_c[v]);
}
/* we now construct the bipartie graph B of G
B(G)=(Y,Z, F) such that
- Y and Z are two copies of V
- (y_i,z_i) in F iff (u_i, u_j) in E that is y_i
(resp z_i) represents x_i in Y (resp z_i in Z) */
forall_nodes(u, G){
set_Y.insert(Y[u]=B.new_node());
set_Z.insert(Z[u]=B.new_node());
}
Z_inverse.init(B,NULL);
/* Z_inverse gives u assigned to z */
forall_nodes(u,G){
Z_inverse[Z[u]]=u;
}
/* connecte y_i to each zj iff there is a path from xi to xj*/
forall_nodes(u, G){
down=G_down[u];
forall(v, down){
// if(u!=v)
B.new_edge(Y[u], Z[v]);
}
}
couple.init(B, NULL);
/* we search for the maximal cardinality matching of B
because it corresponds to a minimum chain partitions */
l_Y=set_to_list<node> (set_Y);
l_Z=set_to_list<node>(set_Z);
card_match=MAX_CARD_BIPARTITE_MATCHING(B, l_Y, l_Z);
forall(e, card_match){
yi=B.source(e);
zi=B.target(e);
couple[yi]=zi;
couple[zi]=yi;
}
/* antichain max = number of minimal chains */
/* now we construct the minimal chain partition */
forall(zj, set_Z){
if (couple[zj]==NULL){ /* we have a source of a new chain */
unsaturated.insert(Z_inverse[zj]);
Ci.clear();
Ci.append(xj=Z_inverse[zj]);
chain[xj]=i;
yj=Y[xj];
while(couple[yj]!=NULL){
zk=couple[yj];
xk=Z_inverse [zk];
Ci.append(xk);
chain[xk]=i;
yj=Y[xk];
}
i++;
}
}
/* now we have the minimal partition in chain*/
return (i);
}