44// (See accompanying file LICENSE_1_0.txt or the copy at
55// http://www.boost.org/LICENSE_1_0.txt)
66
7+ #include < algorithm>
78#include < fstream>
89#include < iostream>
910#include < map>
11+ #include < random>
12+ #include < stdexcept>
1013#include < vector>
1114#include < string>
1215#include < boost/array.hpp>
2730
2831#include < boost/graph/iteration_macros.hpp>
2932
30- typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS,
31- boost::no_property, boost::property< boost::edge_weight_t , int > >
32- undirected_graph;
33- typedef boost::property_map< undirected_graph, boost::edge_weight_t >::type
34- weight_map_type;
35- typedef boost::property_traits< weight_map_type >::value_type weight_type;
33+ #include " mas_sw_maxflow_oracle.hpp"
34+
35+ using mas_sw_oracle::undirected_graph;
36+ using mas_sw_oracle::weight_map_type;
37+ using mas_sw_oracle::weight_type;
38+ using mas_sw_oracle::vertex_descriptor;
39+ using mas_sw_oracle::edge_descriptor;
3640
3741typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS >
3842 undirected_unweighted_graph;
@@ -570,6 +574,169 @@ void test9_weights_start_vertex() {
570574 );
571575}
572576
577+ using cv_distances_map_type = boost::shared_array_property_map< weight_type,
578+ boost::property_map< undirected_graph, boost::vertex_index_t >::const_type >;
579+ using cv_index_in_heap_type = std::vector< vertex_descriptor >::size_type;
580+ using cv_indices_map_type = boost::shared_array_property_map< cv_index_in_heap_type,
581+ boost::property_map< undirected_graph, boost::vertex_index_t >::const_type >;
582+ using cv_maxheap_type = boost::d_ary_heap_indirect< vertex_descriptor, 4 , cv_indices_map_type,
583+ cv_distances_map_type, std::greater< weight_type > >;
584+
585+ // Build the keyed max priority queue MAS runs on: reach counts plus heap positions.
586+ cv_maxheap_type make_weighted_maxheap (const undirected_graph& g)
587+ {
588+ auto distances_map = boost::make_shared_array_property_map (num_vertices (g), weight_type (0 ), get (boost::vertex_index, g));
589+ auto indices_map = boost::make_shared_array_property_map (num_vertices (g), cv_index_in_heap_type (-1 ), get (boost::vertex_index, g));
590+ return cv_maxheap_type (distances_map, indices_map);
591+ }
592+
593+ // Check invariants on a MAS run
594+ void check_visit_order_invariants (
595+ const undirected_graph& g,
596+ const std::vector< vertex_descriptor >& order,
597+ const std::vector< weight_type >& reach)
598+ {
599+ const std::size_t n = num_vertices (g);
600+
601+ // 1) the order is a permutation of all vertices
602+ std::vector< vertex_descriptor > sorted = order;
603+ std::sort (sorted.begin (), sorted.end ());
604+ for (std::size_t i = 0 ; i < sorted.size (); ++i)
605+ BOOST_TEST_EQ (sorted[i], static_cast < vertex_descriptor >(i));
606+
607+ // 2) MAS adds n+1 to the start vertex key to force it first, so its recorded reach is n+1
608+ BOOST_TEST_EQ (reach[0 ], static_cast < weight_type >(n + 1 ));
609+
610+ // only the start vertex is visited before the loop
611+ std::vector< bool > visited (n, false );
612+ visited[order[0 ]] = true ;
613+ auto weight_map = get (boost::edge_weight, g);
614+
615+ // 3) recompute each later vertex reach independently and check MAS agrees
616+ for (std::size_t i = 1 ; i < order.size (); ++i)
617+ {
618+ const vertex_descriptor u = order[i];
619+ // sum the weights of u's edges that lead back into the visited set
620+ weight_type expected = 0 ;
621+ boost::graph_traits< undirected_graph >::out_edge_iterator oi, oi_end;
622+ for (boost::tie (oi, oi_end) = out_edges (u, g); oi != oi_end; ++oi)
623+ if (visited[target (*oi, g)])
624+ expected += get (weight_map, *oi);
625+
626+ BOOST_TEST_EQ (reach[i], expected);
627+ visited[u] = true ;
628+ }
629+ }
630+
631+ // Records every visitor event.
632+ class recording_visitor : public boost ::default_mas_visitor
633+ {
634+ public:
635+ recording_visitor (
636+ std::size_t & initialize_count,
637+ std::size_t & examine_count,
638+ std::vector< vertex_descriptor >& start_order,
639+ std::vector< vertex_descriptor >& finish_order
640+ )
641+ :
642+ initialize_count_ (initialize_count),
643+ examine_count_ (examine_count),
644+ start_order_ (start_order),
645+ finish_order_ (finish_order)
646+ {}
647+
648+ void initialize_vertex (vertex_descriptor, const undirected_graph&) { ++initialize_count_; }
649+ void start_vertex (vertex_descriptor u, const undirected_graph&) { start_order_.push_back (u); }
650+ void examine_edge (edge_descriptor, const undirected_graph&) { ++examine_count_; }
651+ void finish_vertex (vertex_descriptor u, const undirected_graph&) { finish_order_.push_back (u); }
652+
653+ private:
654+ std::size_t & initialize_count_;
655+ std::size_t & examine_count_;
656+ std::vector< vertex_descriptor >& start_order_;
657+ std::vector< vertex_descriptor >& finish_order_;
658+ };
659+
660+ // Cross-validation against the max-flow oracle
661+ // for the last two visited vertices s (second-last) and t (last),
662+ // the reach count of t equals its weighted degree and equals the min s-t cut.
663+ void test_maxflow_crossvalidation ()
664+ {
665+ // sweep a few graph sizes and several seeds
666+ const std::size_t sizes[] = { 6 , 8 , 10 , 12 , 50 };
667+ for (std::size_t si = 0 ; si < sizeof (sizes) / sizeof (sizes[0 ]); ++si)
668+ {
669+ const std::size_t n = sizes[si];
670+ for (std::size_t seed = 1 ; seed <= 25 ; ++seed)
671+ {
672+ // build a deterministic random connected graph
673+ std::mt19937 rng (static_cast < std::mt19937::result_type >(seed));
674+ const undirected_graph g = mas_sw_oracle::make_random_connected_graph (n, n, rng);
675+ BOOST_TEST (mas_sw_oracle::is_connected (g));
676+
677+ // run MAS, recording visit order and reach counts
678+ cv_maxheap_type pq = make_weighted_maxheap (g);
679+ mas_test_visitor< undirected_graph, cv_maxheap_type > vis (pq);
680+ boost::maximum_adjacency_search (g, boost::weight_map (get (boost::edge_weight, g)).visitor (vis).max_priority_queue (pq));
681+
682+ const std::vector< vertex_descriptor >& order = vis.vertex_visit_order ();
683+ const std::vector< weight_type >& reach = vis.vertex_weights_when_visited ();
684+ BOOST_TEST_EQ (order.size (), n);
685+
686+ // take the last two visited vertices s and t
687+ const std::size_t last = order.size () - 1 ;
688+ const vertex_descriptor s = order[last - 1 ];
689+ const vertex_descriptor t = order[last];
690+ const weight_type reach_of_t = reach[last];
691+
692+ // t's reach must equal its weighted degree and the min s-t cut
693+ BOOST_TEST_EQ (reach_of_t , mas_sw_oracle::weighted_degree (g, t));
694+ BOOST_TEST_EQ (reach_of_t , mas_sw_oracle::undirected_min_cut (g, s, t));
695+
696+ // and the whole order must be a valid maximum adjacency ordering
697+ check_visit_order_invariants (g, order, reach);
698+ }
699+ }
700+ }
701+
702+ // Every visitor event fires the expected number of times
703+ void test_visitor_events ()
704+ {
705+ std::mt19937 rng (7 );
706+ const undirected_graph g = mas_sw_oracle::make_random_connected_graph (8 , 8 , rng);
707+
708+ cv_maxheap_type pq = make_weighted_maxheap (g);
709+ std::size_t initialize_count = 0 ;
710+ std::size_t examine_count = 0 ;
711+ std::vector< vertex_descriptor > start_order;
712+ std::vector< vertex_descriptor > finish_order;
713+ recording_visitor vis (initialize_count, examine_count, start_order, finish_order);
714+
715+ boost::maximum_adjacency_search (g, boost::weight_map (get (boost::edge_weight, g)).visitor (vis).max_priority_queue (pq));
716+
717+ BOOST_TEST_EQ (initialize_count, static_cast < std::size_t >(num_vertices (g)));
718+ BOOST_TEST_EQ (examine_count, static_cast < std::size_t >(2 * num_edges (g)));
719+ BOOST_TEST_EQ (start_order.size (), static_cast < std::size_t >(num_vertices (g)));
720+ BOOST_TEST_EQ (finish_order.size (), static_cast < std::size_t >(num_vertices (g)));
721+ BOOST_TEST_ALL_EQ (start_order.begin (), start_order.end (), finish_order.begin (), finish_order.end ());
722+ }
723+
724+ // Precondition violations throw.
725+ void test_exceptions ()
726+ {
727+ // a graph with fewer than two vertices is rejected
728+ undirected_graph too_small;
729+ add_vertex (too_small);
730+ BOOST_TEST_THROWS (boost::maximum_adjacency_search (too_small, boost::weight_map (get (boost::edge_weight, too_small))), boost::bad_graph);
731+
732+ // a non-empty priority queue is rejected
733+ std::mt19937 rng (3 );
734+ const undirected_graph g = mas_sw_oracle::make_random_connected_graph (4 , 4 , rng);
735+ cv_maxheap_type pq = make_weighted_maxheap (g);
736+ pq.push (0 );
737+ BOOST_TEST_THROWS (boost::maximum_adjacency_search (g, boost::weight_map (get (boost::edge_weight, g)).max_priority_queue (pq)), std::invalid_argument);
738+ }
739+
573740#include < boost/graph/iteration_macros_undef.hpp>
574741
575742int main (int argc, char * argv[])
@@ -586,6 +753,9 @@ int main(int argc, char* argv[])
586753 test7_weights ();
587754 test8_weights ();
588755 test9_weights_start_vertex ();
756+ test_maxflow_crossvalidation ();
757+ test_visitor_events ();
758+ test_exceptions ();
589759 }
590760 return boost::report_errors ();
591761}
0 commit comments