// Copyright (c) 2022, ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de) #include "base/graph_cut.h" #include #include #include #include extern "C" { #include "metis.h" } #include "util/logging.h" namespace colmap { namespace { // Wrapper class for weighted, undirected Metis graph. class MetisGraph { public: MetisGraph(const std::vector>& edges, const std::vector& weights) { std::unordered_map>> adjacency_list; for (size_t i = 0; i < edges.size(); ++i) { const auto& edge = edges[i]; const auto weight = weights[i]; const int vertex_idx1 = GetVertexIdx(edge.first); const int vertex_idx2 = GetVertexIdx(edge.second); adjacency_list[vertex_idx1].emplace_back(vertex_idx2, weight); adjacency_list[vertex_idx2].emplace_back(vertex_idx1, weight); } xadj_.reserve(vertex_id_to_idx_.size() + 1); adjncy_.reserve(2 * edges.size()); adjwgt_.reserve(2 * edges.size()); idx_t edge_idx = 0; for (size_t i = 0; i < vertex_id_to_idx_.size(); ++i) { xadj_.push_back(edge_idx); if (adjacency_list.count(i) == 0) { continue; } for (const auto& edge : adjacency_list[i]) { edge_idx += 1; adjncy_.push_back(edge.first); adjwgt_.push_back(edge.second); } } xadj_.push_back(edge_idx); CHECK_EQ(edge_idx, 2 * edges.size()); CHECK_EQ(xadj_.size(), vertex_id_to_idx_.size() + 1); CHECK_EQ(adjncy_.size(), 2 * edges.size()); CHECK_EQ(adjwgt_.size(), 2 * edges.size()); nvtxs = vertex_id_to_idx_.size(); xadj = xadj_.data(); adjncy = adjncy_.data(); vwgt = nullptr; adjwgt = adjwgt_.data(); } int GetVertexIdx(const int id) { const auto it = vertex_id_to_idx_.find(id); if (it == vertex_id_to_idx_.end()) { const int idx = vertex_id_to_idx_.size(); vertex_id_to_idx_.emplace(id, idx); vertex_idx_to_id_.emplace(idx, id); return idx; } else { return it->second; } } int GetVertexId(const int idx) { return vertex_idx_to_id_.at(idx); } idx_t nvtxs = 0; idx_t* xadj = nullptr; idx_t* vwgt = nullptr; idx_t* adjncy = nullptr; idx_t* adjwgt = nullptr; private: std::unordered_map vertex_id_to_idx_; std::unordered_map vertex_idx_to_id_; std::vector xadj_; std::vector adjncy_; std::vector adjwgt_; }; } // namespace void ComputeMinGraphCutStoerWagner( const std::vector>& edges, const std::vector& weights, int* cut_weight, std::vector* cut_labels) { CHECK_EQ(edges.size(), weights.size()); CHECK_GE(edges.size(), 2); typedef boost::property edge_weight_t; typedef boost::adjacency_list undirected_graph_t; int max_vertex_index = 0; for (const auto& edge : edges) { CHECK_GE(edge.first, 0); CHECK_GE(edge.second, 0); max_vertex_index = std::max(max_vertex_index, edge.first); max_vertex_index = std::max(max_vertex_index, edge.second); } const undirected_graph_t graph(edges.begin(), edges.end(), weights.begin(), max_vertex_index + 1, edges.size()); const auto parities = boost::make_one_bit_color_map( boost::num_vertices(graph), boost::get(boost::vertex_index, graph)); *cut_weight = boost::stoer_wagner_min_cut(graph, boost::get(boost::edge_weight, graph), boost::parity_map(parities)); cut_labels->resize(boost::num_vertices(graph)); for (size_t i = 0; i < boost::num_vertices(graph); ++i) { (*cut_labels)[i] = boost::get(parities, i); } } std::unordered_map ComputeNormalizedMinGraphCut( const std::vector>& edges, const std::vector& weights, const int num_parts) { CHECK(!edges.empty()); CHECK_EQ(edges.size(), weights.size()); CHECK_GT(num_parts, 0); MetisGraph graph(edges, weights); idx_t ncon = 1; idx_t edgecut = -1; idx_t nparts = num_parts; idx_t metisOptions[METIS_NOPTIONS]; METIS_SetDefaultOptions(metisOptions); std::vector cut_labels(graph.nvtxs, -1); const int metisResult = METIS_PartGraphKway( &graph.nvtxs, /*ncon=*/&ncon, graph.xadj, graph.adjncy, /*vwgt=*/nullptr, /*vsize=*/nullptr, graph.adjwgt, &nparts, /*tpwgts=*/nullptr, /*ubvec=*/nullptr, metisOptions, &edgecut, cut_labels.data()); if (metisResult == METIS_ERROR_INPUT) { LOG(FATAL) << "INTERNAL: Metis input error"; } else if (metisResult == METIS_ERROR_MEMORY) { LOG(FATAL) << "INTERNAL: Metis memory error"; } else if (metisResult == METIS_ERROR) { LOG(FATAL) << "INTERNAL: Metis 'some other type of error'"; } std::unordered_map labels; for (size_t idx = 0; idx < cut_labels.size(); ++idx) { labels.emplace(graph.GetVertexId(idx), cut_labels[idx]); } return labels; } } // namespace colmap