![]() |
Home | Libraries | License | Support | People | ALPS Web Site |
Table of Contents
The nisy
class template
is a graph adaptor. It provides functionalities to
A canonical label C is a (special) labeling / ordering
of the vertices. The canonical labels C(G) and C(H)
of two graphs G and H are equivalent
if and only if G and H are isomorphic.
The nisy
class template
implements the canonical label as the minimal adjacency matrix, which results
if all vertices are permuted. To make these matrices comparable, they are
interpreted as n x n-bit binary number by concatenating
all rows.
We consider the two graphs
A - B A B | / | | X | C - D C - D
and compute the partition containing the ordering of the respective canonical labels. A partiton is a set of sets which contains each vertex descriptor exactly once.
{( A )( D )( B )( C )} {( A )( B )( C )( D )}
If we order the graphs in the order of the canonical partition, it is easy to see, that they are isomorphic.
A D A B | X | | X | B - C C - D
Now, we can also find the isomorphism between the two graphs:
(A->A) (B->C) (C->D) (D->B)
The automorphism group induces a special partition on the original graph: the orbit partition. Every cell of the orbit partition contains one orbit of the automorphisms group of the original graph.
Let us consider the following graph:
A - B | / | C - D
The orbit partition of this graph is
{(A, D), (B, C)}.
This means, that you can exchange A and D or B and C and the resulting graph is isomorphic to the original one (see example).
The calculation of the canonical label is quite expensive. Therefore, it
is only done if needed. The canonical label and the orbit are cached. If
the original graph is mutated, the canonical label and the orbit change.
This means, that if the original graph is mutated, the cache of the nisy
needs to be cleared. It can be done
with the function clear_cache
.
If the vertices of the original graph are colored, the coloring needs to be translated into a coloring partition. The coloring partition must satisfy the following condition: Two vertices have the same color if and only if they are in the same cell of the coloring partition (see example below).
The nisy
is public derived
from the original graph. It models any concept modelled by the original
graph. The adaptor only takes a reference to the original graph, not a
copy.
The original graph must model the concepts VertexListGraph
and AdjacencyGraph
.
Only undirected graphs without any edge coloring are supported by the
nisy
graph adaptor.
The nisy
determines the
canonical label based on the Nauty
algorithm described by Brendan
McKay in B. D. McKay, Practical graph isomorphism, 10th.
Manitoba Conference on Numerical Mathematics and Computing (Winnipeg, 1980);
Congressus Numerantium, 30 (1981) 45-87.
The functionality supported by nisy
depends on the underlying Graph
type. The nisy
class template
is public derived form Graph, so all types and member
functions are inherited.
template < class graph_type , class vertex_color_type = void , class edge_color_type = void > class nisy { public: typedef typename std::list<std::list<vertex_descriptor_type> > partition_type; typedef typename partition_type::iterator partition_iterator_type; typedef typename /*implementation-defined*/ canonical_label_type; // LessThanComparable typedef typename /*implementation-defined*/ canonical_ordering_iterator; // ForwardIterator nisy( graph_type const & graph ); // only use iif(vertex_color_type == void and edge_color_type == void) template < class color_property_map_type > nisy( graph_type const & graph , vertex_color_property_map_type vertex_property ); // only use iif(vertex_color_type != void and edge_color_type == void) template < class color_property_map_type > nisy( graph_type const & graph , edge_color_property_map_type edge_property ); // only use iif(vertex_color_type == void and edge_color_type != void) template < class vertex_color_property_map_type , class edge_color_property_map_type > nisy( graph_type const & graph , vertex_color_property_map_type vertex_property , edge_color_property_map_type edge_property ); // only use iif(vertex_color_type != void and edge_color_type != void) virtual ~nisy(); inline void invalidate(); inline std::pair<canonical_ordering_iterator, canonical_ordering_iterator> get_canonical_ordering() const; inline canonical_label_type const & get_canonical_label() const; inline partition_type const & get_orbit_partition() const; template<class graph_type1> inline bool operator==(nisy<graph_type1, vertex_color_type, edge_color_type> const & T) const; template<class graph_type1> inline bool operator!=(nisy<graph_type1, vertex_color_type, edge_color_type> const & T) const; }; template< class graph_type1 , class graph_type2 , class vertex_coloring_type1 , class vertex_coloring_type2 , class edge_coloring_type1 , class edge_coloring_type2 > inline std::map< typename boost::graph_traits<graph_type1>::vertex_descriptor , typename boost::graph_traits<graph_type2>::vertex_descriptor > isomorphism( nisy<graph_type1, vertex_coloring_type1, edge_coloring_type1> const & T1 , nisy<graph_type2, vertex_coloring_type2, edge_coloring_type2> const & T2 );
As a first example, we take two uncolored graphs with 4 vertices and 5 edges. We check if the two graphs are isomorphic and if so, we compute the canonical label and the orbit partition of the two graphs. An orbit partiton contains all 'structural identical' vertices. This means, we can permute the vertices which lie in the same orbit without changing the canonical label and in this way generate isomorphic graphs. At the end we generate the explicit isomorphism between the two graphs.
[cg_simple]
The output shows that the two graphs are isomorphic. In this simple example, we can construct the isomorphism by hand. The vertices with valance two and the vertices with valance three must map onto each other.
The two graphs are isomorphic. Canonical partiton and orbit partition of G 0 3 1 2 {( 1 2 )( 0 3 )} Canonical partition and orbit partition of H 0 1 2 3 {( 0 1 )( 2 3 )} Isomorphism G => H (0->0) (1->2) (2->3) (3->1)
We look at the same example as avove, but this time with colored vertices:
// file: coloring.cpp #include "../src/nisy.hpp" #include <boost/graph/adjacency_list.hpp> #include <boost/property_map.hpp> #include <iostream> typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS > graph_type; typedef std::map< boost::graph_traits<graph_type>::vertex_descriptor , int > map_type; typedef boost::associative_property_map<map_type> property_map_type; enum { A, B, C, D, N }; typedef nisy<graph_type>::cell_type cell_type; typedef nisy<graph_type>::partition_type partition_type; // Write a ordering to cout template<class ordering_iterator> void dump_ordering(std::pair<ordering_iterator, ordering_iterator> const & P) { std::cout << "{"; for (ordering_iterator it = P.first; it != P.second; ++it) std::cout << " " << *it; std::cout << " }" << std::endl; } // Write a partition to cout template<class partition_type> void dump_partition(partition_type const & P) { std::cout << "{"; typename partition_type::const_iterator it1; typename partition_type::value_type::const_iterator it2; for (it1 = P.begin(); it1 != P.end(); ++it1) { std::cout << "("; for (it2 = it1->begin(); it2 != it1->end(); ++it2) std::cout << " " << *it2; std::cout << " )"; } std::cout << "}" << std::endl; } int main() { // create the original graphs graph_type g(N), h(N); /* A - B A B | / | vs. | X | C - D C - D */ add_edge(A, B, g); add_edge(A, C, g); add_edge(B, C, g); add_edge(B, D, g); add_edge(C, D, g); add_edge(A, C, h); add_edge(A, D, h); add_edge(B, C, h); add_edge(B, D, h); add_edge(C, D, h); // create coloring map_type map_g, map_h; property_map_type pmap_g(map_g), pmap_h(map_h); // create coloring of g boost::put(pmap_g, static_cast<int>(A), 0); boost::put(pmap_g, static_cast<int>(B), 0); boost::put(pmap_g, static_cast<int>(C), 1); boost::put(pmap_g, static_cast<int>(D), 1); // create coloring of h boost::put(pmap_h, static_cast<int>(A), 0); boost::put(pmap_h, static_cast<int>(B), 1); boost::put(pmap_h, static_cast<int>(C), 0); boost::put(pmap_h, static_cast<int>(D), 1); // create comparable adaptors nisy<graph_type, int> cg(g, pmap_g), ch(h, pmap_h); // check if the graphs g and h are isomorphic if (cg == ch) std::cout << "The two graphs are isomorphic." << std::endl; else std::cout << "The two graphs are not isomorphic." << std::endl; // dump canonical partiton and arbit of g std::cout << std::endl; std::cout << "Canonical partiton and orbit partition of G" << std::endl; dump_ordering(cg.get_canonical_ordering()); dump_partition(cg.get_orbit_partition()); // dump canonical partition and arbit of h std::cout << std::endl; std::cout << "Canonical partition and orbit partition of H" << std::endl; dump_ordering(ch.get_canonical_ordering()); dump_partition(ch.get_orbit_partition()); // compute the isomprphism between the two grphas typedef std::map< boost::graph_traits<graph_type>::vertex_descriptor, boost::graph_traits<graph_type>::vertex_descriptor > isomorphism_type; isomorphism_type iso(isomorphism(cg, ch)); // write the isomprphism to cout std::cout << std::endl << "Isomorphism G => H" << std::endl; for (isomorphism_type::iterator it = iso.begin(); it != iso.end(); ++it) std::cout << "(" << it->first << "->" << it->second << ") "; std::cout << std::endl; return EXIT_SUCCESS; }
The graphs are not isomorphic anymore:
The two graphs are not isomorphic. Canonical partiton and orbit partition of G { 0 1 3 2 } {( 0 )( 1 )( 2 )( 3 )} Canonical partition and orbit partition of H { 0 2 1 3 } {( 0 )( 1 )( 2 )( 3 )} Isomorphism G => H (0->0) (1->2) (2->3) (3->1)
A more advanced example creates all graphs up to a given number of edges and counts the number of nonisomorphic graphs with a specific number of edges.
We first create a small class template to generate all graphs with a given number of vertices.
This class template takes all graphs with one endge less than the requested
ones and tries to add vertices and edges at all possible places. For each
new graph, the canonical label is computed. If the label is not available
in the list, the graph is kept, else it is discarded. The std::set
allows a binary search over all checked
graphs.
// file: nisy.generator.hpp #ifndef NISY_GENERATOR_HPP #define NISY_GENERATOR_HPP #include "../src/nisy.hpp" template <class graph_type> class graph_generator { typedef typename boost::graph_traits< graph_type >::vertex_iterator vertex_iterator; public: // Iterator over all graphs with given #edges typedef typename std::vector<graph_type>::const_iterator iterator; // Defaultconstructor graph_generator() : graphs_(1, std::vector<graph_type>(1, graph_type())) { add_vertex(graphs_[0][0]); } // generates all graphs with edge_size edges std::pair<iterator, iterator> generate( std::size_t edge_size ) { graph_type *graph, new_graph; typename std::vector<graph_type>::iterator it; vertex_iterator it1, end1, it2, end2; // construct all grophs with N edges from the Graphs with N-1 edges while (graphs_.size() <= edge_size) { graphs_.push_back(std::vector<graph_type>()); it = (graphs_.end() - 2)->begin(); while ((graph=(it==(graphs_.end()-2)->end()?NULL:&(*it++)))!=NULL) for (tie(it1, end1) = vertices(*graph); it1 != end1; ++it1) { new_graph = graph_type(*graph); add_edge(*it1, add_vertex(new_graph), new_graph); check_graph(new_graph); for (tie(it2, end2) = vertices(*graph); it2 != end2; ++it2) if (*it1 < *it2 && !edge(*it1, *it2, *graph).second) { new_graph = graph_type(*graph); add_edge(*it1, *it2, new_graph); check_graph(new_graph); } } } return std::make_pair( graphs_[edge_size].begin(), graphs_[edge_size].end() ); } private: void check_graph(graph_type const & graph) { // create comparable adapter nisy<graph_type> com_graph(graph); // check if the label has alredy been found if (labels_.find(make_tuple(com_graph.get_canonical_label().size(), com_graph.get_canonical_label())) == labels_.end()) { labels_.insert(make_tuple(com_graph.get_canonical_label().size(), com_graph.get_canonical_label())); graphs_.back().push_back(graph); } } std::vector<std::vector<graph_type> > graphs_; // list of all lables already checked. std::set<boost::tuple<std::size_t, typename nisy<graph_type>::canonical_label_type> > labels_; }; #endif // NISY_GENERATOR_HPP
With the class template above, we only need to loop over all numbers of
edges we need. Since the iterators of the std::vector
are RandomAccess
iterators
we can get the numbers of graphs by subtracting the first from the one
past the last. We use the boost::timer
to measure the time needed in seconds.
// file: nisy.generator.cpp #include "nisy.generator.hpp" #include <iostream> #include <iomanip> #include <boost/timer.hpp> #include <boost/tuple/tuple.hpp> #include <boost/graph/adjacency_list.hpp> #define MAX_GRAPH_SIZE 12 typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS > graph_type; int main() { // construct generator graph_generator<graph_type> generator; graph_generator<graph_type>::iterator it, end; boost::timer timer; std::cout << "#edges #graphs time[s]" << std::endl; for (std::size_t i = 0; i <= MAX_GRAPH_SIZE; ++i) { // generate the graphs boost::tie(it, end) = generator.generate(i); // write the result to cout std::cout << std::setw(6) << i << " " << std::setw(7) << (end - it) << " " << std::setw(7) << static_cast<std::size_t>(timer.elapsed()+.5) << std::endl; } return EXIT_SUCCESS; }
The example returns the following table:
#edges #graphs time[s] 0 1 0 1 1 0 2 1 0 3 3 0 4 5 0 5 12 0 6 30 0 7 79 0 8 227 1 9 710 3 10 2323 12 11 8073 51 12 29511 232
Last revised: April 27, 2012 at 09:04:07 GMT |