Add an unweighted graph. Rename Graph -> WeightedGraph. Add a new Graph class, which is cheaper to construct and work with if the weights are not needed. This cuts down the cost of building the Hessian graph significantly. Change-Id: Id0cfc81dd2c0bb5ff8f63a1b55aa133c53c0c869
diff --git a/internal/ceres/canonical_views_clustering.cc b/internal/ceres/canonical_views_clustering.cc index 2f032e6..9bbab4b 100644 --- a/internal/ceres/canonical_views_clustering.cc +++ b/internal/ceres/canonical_views_clustering.cc
@@ -61,7 +61,7 @@ // vertices may not be assigned to any cluster. In this case they // are assigned to a cluster with id = kInvalidClusterId. void ComputeClustering(const CanonicalViewsClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, vector<int>* centers, IntMap* membership); @@ -74,7 +74,7 @@ IntMap* membership) const; CanonicalViewsClusteringOptions options_; - const Graph<int>* graph_; + const WeightedGraph<int>* graph_; // Maps a view to its representative canonical view (its cluster // center). IntMap view_to_canonical_view_; @@ -85,7 +85,7 @@ void ComputeCanonicalViewsClustering( const CanonicalViewsClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, vector<int>* centers, IntMap* membership) { time_t start_time = time(NULL); @@ -98,7 +98,7 @@ // Implementation of CanonicalViewsClustering void CanonicalViewsClustering::ComputeClustering( const CanonicalViewsClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, vector<int>* centers, IntMap* membership) { options_ = options; @@ -150,7 +150,7 @@ for (IntSet::const_iterator view = views.begin(); view != views.end(); ++view) { - if (graph_->VertexWeight(*view) != Graph<int>::InvalidWeight()) { + if (graph_->VertexWeight(*view) != WeightedGraph<int>::InvalidWeight()) { valid_views->insert(*view); } }
diff --git a/internal/ceres/canonical_views_clustering.h b/internal/ceres/canonical_views_clustering.h index 1b4c4ee..d3fa572 100644 --- a/internal/ceres/canonical_views_clustering.h +++ b/internal/ceres/canonical_views_clustering.h
@@ -101,7 +101,7 @@ // cluster. In this case they are assigned to a cluster with id = -1; void ComputeCanonicalViewsClustering( const CanonicalViewsClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, vector<int>* centers, HashMap<int, int>* membership);
diff --git a/internal/ceres/canonical_views_clustering_test.cc b/internal/ceres/canonical_views_clustering_test.cc index f86084a..006b2dd 100644 --- a/internal/ceres/canonical_views_clustering_test.cc +++ b/internal/ceres/canonical_views_clustering_test.cc
@@ -75,7 +75,7 @@ ComputeCanonicalViewsClustering(options_, graph_, ¢ers_, &membership_); } - Graph<int> graph_; + WeightedGraph<int> graph_; CanonicalViewsClusteringOptions options_; vector<int> centers_;
diff --git a/internal/ceres/graph.h b/internal/ceres/graph.h index 5f92d4d..38fdae3 100644 --- a/internal/ceres/graph.h +++ b/internal/ceres/graph.h
@@ -43,13 +43,75 @@ namespace ceres { namespace internal { -// A weighted undirected graph templated over the vertex ids. Vertex +// A unweighted undirected graph templated over the vertex ids. Vertex // should be hashable and comparable. template <typename Vertex> class Graph { public: Graph() {} + // Add a vertex. + void AddVertex(const Vertex& vertex) { + if (vertices_.insert(vertex).second) { + edges_[vertex] = HashSet<Vertex>(); + } + } + + bool RemoveVertex(const Vertex& vertex) { + if (vertices_.find(vertex) == vertices_.end()) { + return false; + } + + vertices_.erase(vertex); + const HashSet<Vertex>& sinks = edges_[vertex]; + for (typename HashSet<Vertex>::const_iterator it = sinks.begin(); + it != sinks.end(); ++it) { + edges_[*it].erase(vertex); + } + + edges_.erase(vertex); + return true; + } + + // Add an edge between the vertex1 and vertex2. Calling AddEdge on a + // pair of vertices which do not exist in the graph yet will result + // in undefined behavior. + // + // It is legal to call this method repeatedly for the same set of + // vertices. + void AddEdge(const Vertex& vertex1, const Vertex& vertex2) { + DCHECK(vertices_.find(vertex1) != vertices_.end()); + DCHECK(vertices_.find(vertex2) != vertices_.end()); + + if (edges_[vertex1].insert(vertex2).second) { + edges_[vertex2].insert(vertex1); + } + } + + // Calling Neighbors on a vertex not in the graph will result in + // undefined behaviour. + const HashSet<Vertex>& Neighbors(const Vertex& vertex) const { + return FindOrDie(edges_, vertex); + } + + const HashSet<Vertex>& vertices() const { + return vertices_; + } + + private: + HashSet<Vertex> vertices_; + HashMap<Vertex, HashSet<Vertex> > edges_; + + CERES_DISALLOW_COPY_AND_ASSIGN(Graph); +}; + +// A weighted undirected graph templated over the vertex ids. Vertex +// should be hashable and comparable. +template <typename Vertex> +class WeightedGraph { + public: + WeightedGraph() {} + // Add a weighted vertex. If the vertex already exists in the graph, // its weight is set to the new weight. void AddVertex(const Vertex& vertex, double weight) { @@ -152,7 +214,7 @@ HashMap<Vertex, HashSet<Vertex> > edges_; HashMap<pair<Vertex, Vertex>, double> edge_weights_; - CERES_DISALLOW_COPY_AND_ASSIGN(Graph); + CERES_DISALLOW_COPY_AND_ASSIGN(WeightedGraph); }; } // namespace internal
diff --git a/internal/ceres/graph_algorithms.h b/internal/ceres/graph_algorithms.h index ca3a2fe..46a37c5 100644 --- a/internal/ceres/graph_algorithms.h +++ b/internal/ceres/graph_algorithms.h
@@ -38,6 +38,7 @@ #include <utility> #include "ceres/collections_port.h" #include "ceres/graph.h" +#include "ceres/wall_time.h" #include "glog/logging.h" namespace ceres { @@ -171,6 +172,8 @@ template <typename Vertex> int StableIndependentSetOrdering(const Graph<Vertex>& graph, vector<Vertex>* ordering) { + EventLogger event_logger("StableIndependentSetOrdering"); + CHECK_NOTNULL(ordering); const HashSet<Vertex>& vertices = graph.vertices(); const int num_vertices = vertices.size(); @@ -185,6 +188,7 @@ stable_sort(vertex_queue.begin(), vertex_queue.end(), VertexDegreeLessThan<Vertex>(graph)); + event_logger.AddEvent("StableSort"); // Mark all vertices white. HashMap<Vertex, char> vertex_color; @@ -193,6 +197,7 @@ ++it) { vertex_color[*it] = kWhite; } + event_logger.AddEvent("MarkWhite"); ordering->clear(); ordering->reserve(num_vertices); @@ -213,6 +218,7 @@ vertex_color[*it] = kGrey; } } + event_logger.AddEvent("IndependentVertices"); int independent_set_size = ordering->size(); @@ -228,6 +234,7 @@ ordering->push_back(vertex); } } + event_logger.AddEvent("DependentVertices"); CHECK_EQ(ordering->size(), num_vertices); return independent_set_size; @@ -270,11 +277,11 @@ // spanning forest, or a collection of linear paths that span the // graph G. template <typename Vertex> -Graph<Vertex>* -Degree2MaximumSpanningForest(const Graph<Vertex>& graph) { +WeightedGraph<Vertex>* +Degree2MaximumSpanningForest(const WeightedGraph<Vertex>& graph) { // Array of edges sorted in decreasing order of their weights. vector<pair<double, pair<Vertex, Vertex> > > weighted_edges; - Graph<Vertex>* forest = new Graph<Vertex>(); + WeightedGraph<Vertex>* forest = new WeightedGraph<Vertex>(); // Disjoint-set to keep track of the connected components in the // maximum spanning tree.
diff --git a/internal/ceres/graph_algorithms_test.cc b/internal/ceres/graph_algorithms_test.cc index 7c24476..bbb1e0d 100644 --- a/internal/ceres/graph_algorithms_test.cc +++ b/internal/ceres/graph_algorithms_test.cc
@@ -102,13 +102,13 @@ } TEST(Degree2MaximumSpanningForest, PreserveWeights) { - Graph<int> graph; + WeightedGraph<int> graph; graph.AddVertex(0, 1.0); graph.AddVertex(1, 2.0); graph.AddEdge(0, 1, 0.5); graph.AddEdge(1, 0, 0.5); - scoped_ptr<Graph<int> > forest(Degree2MaximumSpanningForest(graph)); + scoped_ptr<WeightedGraph<int> > forest(Degree2MaximumSpanningForest(graph)); const HashSet<int>& vertices = forest->vertices(); EXPECT_EQ(vertices.size(), 2); @@ -119,7 +119,7 @@ } TEST(Degree2MaximumSpanningForest, StarGraph) { - Graph<int> graph; + WeightedGraph<int> graph; graph.AddVertex(0); graph.AddVertex(1); graph.AddVertex(2); @@ -131,7 +131,7 @@ graph.AddEdge(0, 3, 3.0); graph.AddEdge(0, 4, 4.0); - scoped_ptr<Graph<int> > forest(Degree2MaximumSpanningForest(graph)); + scoped_ptr<WeightedGraph<int> > forest(Degree2MaximumSpanningForest(graph)); const HashSet<int>& vertices = forest->vertices(); EXPECT_EQ(vertices.size(), 5); @@ -176,8 +176,8 @@ // | // 2-3 // 0,1 and 2 have degree 1 and 3 has degree 2. - graph.AddEdge(0, 1, 1.0); - graph.AddEdge(2, 3, 1.0); + graph.AddEdge(0, 1); + graph.AddEdge(2, 3); VertexTotalOrdering<int> less_than(graph); for (int i = 0; i < 4; ++i) {
diff --git a/internal/ceres/graph_test.cc b/internal/ceres/graph_test.cc index 85b80bf..a1521d8 100644 --- a/internal/ceres/graph_test.cc +++ b/internal/ceres/graph_test.cc
@@ -44,6 +44,51 @@ TEST(Graph, AddVertexAndEdge) { Graph<int> graph; + graph.AddVertex(0); + graph.AddVertex(1); + graph.AddEdge(0, 1); + + const HashSet<int>& vertices = graph.vertices(); + EXPECT_EQ(vertices.size(), 2); + EXPECT_EQ(graph.Neighbors(0).size(), 1); + EXPECT_EQ(graph.Neighbors(1).size(), 1); +} + +TEST(Graph, AddVertexIdempotence) { + Graph<int> graph; + graph.AddVertex(0); + graph.AddVertex(1); + graph.AddEdge(0, 1) + + const HashSet<int>& vertices = graph.vertices(); + + EXPECT_EQ(vertices.size(), 2); + + // Try adding the vertex again with a new weight. + graph.AddVertex(0, 3.0); + EXPECT_EQ(vertices.size(), 2); + + // Rest of the graph remains the same. + EXPECT_EQ(graph.Neighbors(0).size(), 1); + EXPECT_EQ(graph.Neighbors(1).size(), 1); +} + +TEST(Graph, DieOnNonExistentVertex) { + Graph<int> graph; + graph.AddVertex(0); + graph.AddVertex(1); + graph.AddEdge(0, 1); + + EXPECT_DEATH_IF_SUPPORTED(graph.Neighbors(2), "key not found"); +} + +TEST(WeightedGraph, EmptyGraph) { + WeightedGraph<int> graph; + EXPECT_EQ(graph.vertices().size(), 0); +} + +TEST(WeightedGraph, AddVertexAndEdge) { + WeightedGraph<int> graph; graph.AddVertex(0, 1.0); graph.AddVertex(1, 2.0); graph.AddEdge(0, 1, 0.5); @@ -58,8 +103,8 @@ EXPECT_EQ(graph.EdgeWeight(1, 0), 0.5); } -TEST(Graph, AddVertexIdempotence) { - Graph<int> graph; +TEST(WeightedGraph, AddVertexIdempotence) { + WeightedGraph<int> graph; graph.AddVertex(0, 1.0); graph.AddVertex(1, 2.0); graph.AddEdge(0, 1, 0.5); @@ -83,8 +128,8 @@ EXPECT_EQ(graph.EdgeWeight(1, 0), 0.5); } -TEST(Graph, DieOnNonExistentVertex) { - Graph<int> graph; +TEST(WeightedGraph, DieOnNonExistentVertex) { + WeightedGraph<int> graph; graph.AddVertex(0, 1.0); graph.AddVertex(1, 2.0); graph.AddEdge(0, 1, 0.5); @@ -93,8 +138,8 @@ EXPECT_DEATH_IF_SUPPORTED(graph.Neighbors(2), "key not found"); } -TEST(Graph, NonExistentEdge) { - Graph<int> graph; +TEST(WeightedGraph, NonExistentEdge) { + WeightedGraph<int> graph; graph.AddVertex(0, 1.0); graph.AddVertex(1, 2.0); graph.AddEdge(0, 1, 0.5);
diff --git a/internal/ceres/parameter_block_ordering.cc b/internal/ceres/parameter_block_ordering.cc index 3032329..1525de9 100644 --- a/internal/ceres/parameter_block_ordering.cc +++ b/internal/ceres/parameter_block_ordering.cc
@@ -37,6 +37,7 @@ #include "ceres/parameter_block.h" #include "ceres/program.h" #include "ceres/residual_block.h" +#include "ceres/wall_time.h" #include "glog/logging.h" namespace ceres { @@ -45,8 +46,10 @@ int ComputeStableSchurOrdering(const Program& program, vector<ParameterBlock*>* ordering) { CHECK_NOTNULL(ordering)->clear(); - + EventLogger event_logger("ComputeStableSchurOrdering"); scoped_ptr<Graph< ParameterBlock*> > graph(CreateHessianGraph(program)); + event_logger.AddEvent("CreateHessianGraph"); + const vector<ParameterBlock*>& parameter_blocks = program.parameter_blocks(); const HashSet<ParameterBlock*>& vertices = graph->vertices(); for (int i = 0; i < parameter_blocks.size(); ++i) { @@ -54,8 +57,10 @@ ordering->push_back(parameter_blocks[i]); } } + event_logger.AddEvent("Preordering"); int independent_set_size = StableIndependentSetOrdering(*graph, ordering); + event_logger.AddEvent("StableIndependentSet"); // Add the excluded blocks to back of the ordering vector. for (int i = 0; i < parameter_blocks.size(); ++i) { @@ -64,6 +69,7 @@ ordering->push_back(parameter_block); } } + event_logger.AddEvent("ConstantParameterBlocks"); return independent_set_size; } @@ -109,8 +115,7 @@ } } -Graph<ParameterBlock*>* -CreateHessianGraph(const Program& program) { +Graph<ParameterBlock*>* CreateHessianGraph(const Program& program) { Graph<ParameterBlock*>* graph = CHECK_NOTNULL(new Graph<ParameterBlock*>); const vector<ParameterBlock*>& parameter_blocks = program.parameter_blocks(); for (int i = 0; i < parameter_blocks.size(); ++i) {
diff --git a/internal/ceres/single_linkage_clustering.cc b/internal/ceres/single_linkage_clustering.cc index 0a8b20c..f0f7e0e 100644 --- a/internal/ceres/single_linkage_clustering.cc +++ b/internal/ceres/single_linkage_clustering.cc
@@ -44,7 +44,7 @@ int ComputeSingleLinkageClustering( const SingleLinkageClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, HashMap<int, int>* membership) { CHECK_NOTNULL(membership)->clear();
diff --git a/internal/ceres/single_linkage_clustering.h b/internal/ceres/single_linkage_clustering.h index e6fdeab..79c4da1 100644 --- a/internal/ceres/single_linkage_clustering.h +++ b/internal/ceres/single_linkage_clustering.h
@@ -64,7 +64,7 @@ // identified by the algorithm. int ComputeSingleLinkageClustering( const SingleLinkageClusteringOptions& options, - const Graph<int>& graph, + const WeightedGraph<int>& graph, HashMap<int, int>* membership); } // namespace internal
diff --git a/internal/ceres/single_linkage_clustering_test.cc b/internal/ceres/single_linkage_clustering_test.cc index 1cbc5be..1d4efd2 100644 --- a/internal/ceres/single_linkage_clustering_test.cc +++ b/internal/ceres/single_linkage_clustering_test.cc
@@ -43,7 +43,7 @@ namespace internal { TEST(SingleLinkageClustering, GraphHasTwoComponents) { - Graph<int> graph; + WeightedGraph<int> graph; const int kNumVertices = 6; for (int i = 0; i < kNumVertices; ++i) { graph.AddVertex(i); @@ -61,8 +61,7 @@ ComputeSingleLinkageClustering(options, graph, &membership); EXPECT_EQ(membership.size(), kNumVertices); - EXPECT_EQ(membership[1], membership[0]); - EXPECT_EQ(membership[2], membership[0]); + EXPECT_EQ(membership[1], membership[0]); EXPECT_EQ(membership[2], membership[0]); EXPECT_EQ(membership[3], membership[0]); EXPECT_NE(membership[4], membership[0]); EXPECT_NE(membership[5], membership[0]); @@ -70,7 +69,7 @@ } TEST(SingleLinkageClustering, ComponentWithWeakLink) { - Graph<int> graph; + WeightedGraph<int> graph; const int kNumVertices = 6; for (int i = 0; i < kNumVertices; ++i) { graph.AddVertex(i); @@ -99,7 +98,7 @@ } TEST(SingleLinkageClustering, ComponentWithWeakLinkAndStrongLink) { - Graph<int> graph; + WeightedGraph<int> graph; const int kNumVertices = 6; for (int i = 0; i < kNumVertices; ++i) { graph.AddVertex(i);
diff --git a/internal/ceres/visibility.cc b/internal/ceres/visibility.cc index b3ee185..e46421c 100644 --- a/internal/ceres/visibility.cc +++ b/internal/ceres/visibility.cc
@@ -76,7 +76,7 @@ } } -Graph<int>* CreateSchurComplementGraph(const vector<set<int> >& visibility) { +WeightedGraph<int>* CreateSchurComplementGraph(const vector<set<int> >& visibility) { const time_t start_time = time(NULL); // Compute the number of e_blocks/point blocks. Since the visibility // set for each e_block/camera contains the set of e_blocks/points @@ -122,7 +122,7 @@ } } - Graph<int>* graph = new Graph<int>(); + WeightedGraph<int>* graph = new WeightedGraph<int>(); // Add vertices and initialize the pairs for self edges so that self // edges are guaranteed. This is needed for the Canonical views
diff --git a/internal/ceres/visibility.h b/internal/ceres/visibility.h index 5ddd3a5..62d4f0f 100644 --- a/internal/ceres/visibility.h +++ b/internal/ceres/visibility.h
@@ -72,9 +72,9 @@ // matrix/Schur complement matrix obtained by eliminating the e_blocks // from the normal equations. // -// Caller acquires ownership of the returned Graph pointer +// Caller acquires ownership of the returned WeightedGraph pointer // (heap-allocated). -Graph<int>* CreateSchurComplementGraph(const vector<set<int> >& visibility); +WeightedGraph<int>* CreateSchurComplementGraph(const vector<set<int> >& visibility); } // namespace internal } // namespace ceres
diff --git a/internal/ceres/visibility_based_preconditioner.cc b/internal/ceres/visibility_based_preconditioner.cc index 695eedc..c7ed049 100644 --- a/internal/ceres/visibility_based_preconditioner.cc +++ b/internal/ceres/visibility_based_preconditioner.cc
@@ -167,9 +167,9 @@ // maximum spanning forest of this graph. vector<set<int> > cluster_visibility; ComputeClusterVisibility(visibility, &cluster_visibility); - scoped_ptr<Graph<int> > cluster_graph( + scoped_ptr<WeightedGraph<int> > cluster_graph( CHECK_NOTNULL(CreateClusterGraph(cluster_visibility))); - scoped_ptr<Graph<int> > forest( + scoped_ptr<WeightedGraph<int> > forest( CHECK_NOTNULL(Degree2MaximumSpanningForest(*cluster_graph))); ForestToClusterPairs(*forest, &cluster_pairs_); } @@ -189,7 +189,7 @@ // memberships for each camera block. void VisibilityBasedPreconditioner::ClusterCameras( const vector<set<int> >& visibility) { - scoped_ptr<Graph<int> > schur_complement_graph( + scoped_ptr<WeightedGraph<int> > schur_complement_graph( CHECK_NOTNULL(CreateSchurComplementGraph(visibility))); HashMap<int, int> membership; @@ -498,7 +498,7 @@ // Convert a graph into a list of edges that includes self edges for // each vertex. void VisibilityBasedPreconditioner::ForestToClusterPairs( - const Graph<int>& forest, + const WeightedGraph<int>& forest, HashSet<pair<int, int> >* cluster_pairs) const { CHECK_NOTNULL(cluster_pairs)->clear(); const HashSet<int>& vertices = forest.vertices(); @@ -541,9 +541,9 @@ // Construct a graph whose vertices are the clusters, and the edge // weights are the number of 3D points visible to cameras in both the // vertices. -Graph<int>* VisibilityBasedPreconditioner::CreateClusterGraph( +WeightedGraph<int>* VisibilityBasedPreconditioner::CreateClusterGraph( const vector<set<int> >& cluster_visibility) const { - Graph<int>* cluster_graph = new Graph<int>; + WeightedGraph<int>* cluster_graph = new WeightedGraph<int>; for (int i = 0; i < num_clusters_; ++i) { cluster_graph->AddVertex(i);
diff --git a/internal/ceres/visibility_based_preconditioner.h b/internal/ceres/visibility_based_preconditioner.h index 70cea83..7e0b51b 100644 --- a/internal/ceres/visibility_based_preconditioner.h +++ b/internal/ceres/visibility_based_preconditioner.h
@@ -156,8 +156,8 @@ vector<int>* membership_vector) const; void ComputeClusterVisibility(const vector<set<int> >& visibility, vector<set<int> >* cluster_visibility) const; - Graph<int>* CreateClusterGraph(const vector<set<int> >& visibility) const; - void ForestToClusterPairs(const Graph<int>& forest, + WeightedGraph<int>* CreateClusterGraph(const vector<set<int> >& visibility) const; + void ForestToClusterPairs(const WeightedGraph<int>& forest, HashSet<pair<int, int> >* cluster_pairs) const; void ComputeBlockPairsInPreconditioner(const CompressedRowBlockStructure& bs); bool IsBlockPairInPreconditioner(int block1, int block2) const;
diff --git a/internal/ceres/visibility_test.cc b/internal/ceres/visibility_test.cc index 0e22f88..01f2bdf 100644 --- a/internal/ceres/visibility_test.cc +++ b/internal/ceres/visibility_test.cc
@@ -108,7 +108,7 @@ ASSERT_EQ(visibility[i].size(), 1); } - scoped_ptr<Graph<int> > graph(CreateSchurComplementGraph(visibility)); + scoped_ptr<WeightedGraph<int> > graph(CreateSchurComplementGraph(visibility)); EXPECT_EQ(graph->vertices().size(), visibility.size()); for (int i = 0; i < visibility.size(); ++i) { EXPECT_EQ(graph->VertexWeight(i), 1.0); @@ -184,7 +184,7 @@ ASSERT_EQ(visibility[i].size(), 0); } - scoped_ptr<Graph<int> > graph(CreateSchurComplementGraph(visibility)); + scoped_ptr<WeightedGraph<int> > graph(CreateSchurComplementGraph(visibility)); EXPECT_EQ(graph->vertices().size(), visibility.size()); for (int i = 0; i < visibility.size(); ++i) { EXPECT_EQ(graph->VertexWeight(i), 1.0);