mirror of
https://github.com/opencv/opencv.git
synced 2026-07-26 05:43:05 +04:00
59218f9edd
Geometry module #29175 OpenCV Contrib: https://github.com/opencv/opencv_contrib/pull/4129 CI changes: https://github.com/opencv/ci-gha-workflow/pull/313 Continues - https://github.com/opencv/opencv/pull/28804 - https://github.com/opencv/opencv/pull/29101 - https://github.com/opencv/opencv/pull/29108 - https://github.com/opencv/opencv/pull/28810 Todo for followup PRs: - [x] Rename doxygen groups - [x] Fix JS modules layout and whitelists - [ ] Sort tutorials code/snippets ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
403 lines
11 KiB
C++
403 lines
11 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html
|
|
|
|
#include "test_precomp.hpp"
|
|
#include <opencv2/geometry/mst.hpp>
|
|
|
|
namespace opencv_test {
|
|
namespace {
|
|
|
|
using namespace cv;
|
|
|
|
typedef tuple<MSTAlgorithm /*MSTalgorithm*/,
|
|
int /*numNodes*/,
|
|
std::vector<MSTEdge>/*edges*/,
|
|
std::vector<MSTEdge>/*expectedEdges*/
|
|
> MSTParamType;
|
|
typedef testing::TestWithParam<MSTParamType> MST;
|
|
|
|
TEST_P(MST, checkCorrectness)
|
|
{
|
|
const int algorithm = get<0>(GetParam());
|
|
const int numNodes = get<1>(GetParam());
|
|
const std::vector<MSTEdge>& edges = get<2>(GetParam());
|
|
const std::vector<MSTEdge>& expectedEdges = get<3>(GetParam());
|
|
|
|
std::vector<MSTEdge> mstEdges;
|
|
bool result = false;
|
|
|
|
switch (algorithm) {
|
|
case MST_PRIM:
|
|
// Select first node for root
|
|
result = buildMST(numNodes, edges, mstEdges, MST_PRIM, 0);
|
|
break;
|
|
|
|
case MST_KRUSKAL:
|
|
result = buildMST(numNodes, edges, mstEdges, MST_KRUSKAL, 0);
|
|
break;
|
|
|
|
default:
|
|
FAIL() << "Unknown selected MST algorithm: " << algorithm;
|
|
}
|
|
|
|
EXPECT_TRUE(result);
|
|
EXPECT_EQ(mstEdges.size(), expectedEdges.size());
|
|
for (const auto& edge : expectedEdges)
|
|
{
|
|
auto it = std::find_if(mstEdges.begin(), mstEdges.end(), [&edge](const MSTEdge& e) {
|
|
return (e.source == edge.source && e.target == edge.target) ||
|
|
(e.source == edge.target && e.target == edge.source);
|
|
});
|
|
EXPECT_TRUE(it != mstEdges.end()) << "Missing expected edge: "
|
|
<< edge.source << " -> " << edge.target;
|
|
}
|
|
}
|
|
|
|
const MSTParamType mst_graphs[] =
|
|
{
|
|
// Small Graph
|
|
MSTParamType(MST_PRIM, 4,
|
|
{
|
|
{0, 1, 1.0}, {0, 2, 2.0}, {1, 2, 1.5}, {1, 3, 2.5}, {2, 3, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 1.5}, {2, 3, 1.0}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 4,
|
|
{
|
|
{0, 1, 1.0}, {0, 2, 2.0}, {1, 2, 1.5}, {1, 3, 2.5}, {2, 3, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 1.5}, {2, 3, 1.0}
|
|
}
|
|
),
|
|
|
|
// 2 Nodes, 1 Edge
|
|
MSTParamType(MST_PRIM, 2,
|
|
{
|
|
{0, 1, 42.0}
|
|
},
|
|
{
|
|
{0, 1, 42.0}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 2,
|
|
{
|
|
{0, 1, 42.0}
|
|
},
|
|
{
|
|
{0, 1, 42.0}
|
|
}
|
|
),
|
|
|
|
// Dense graph (clique)
|
|
MSTParamType(MST_PRIM, 4,
|
|
{
|
|
{0, 1, 1.0}, {0, 2, 2.0}, {0, 3, 3.0},
|
|
{1, 2, 1.5}, {1, 3, 2.5}, {2, 3, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {2, 3, 1.0}, {1, 2, 1.5}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 4,
|
|
{
|
|
{0, 1, 1.0}, {0, 2, 2.0}, {0, 3, 3.0},
|
|
{1, 2, 1.5}, {1, 3, 2.5}, {2, 3, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {2, 3, 1.0}, {1, 2, 1.5}
|
|
}
|
|
),
|
|
|
|
// Sparse
|
|
MSTParamType(MST_PRIM, 4,
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}, {1, 3, 3.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}, {1, 3, 3.0}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 4,
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}, {1, 3, 3.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}, {1, 3, 3.0}
|
|
}
|
|
),
|
|
|
|
// Weight Floating point check
|
|
MSTParamType(MST_PRIM, 3,
|
|
{
|
|
{0, 1, 1.000001}, {1, 2, 1.000002}, {0, 2, 1.000003}
|
|
},
|
|
{
|
|
{0, 1, 1.000001}, {1, 2, 1.000002}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 3,
|
|
{
|
|
{0, 1, 1.000001}, {1, 2, 1.000002}, {0, 2, 1.000003}
|
|
},
|
|
{
|
|
{0, 1, 1.000001}, {1, 2, 1.000002}
|
|
}
|
|
),
|
|
|
|
// 0 or ~0 weight valuess
|
|
MSTParamType(MST_PRIM, 3,
|
|
{
|
|
{0, 1, 0.0}, {1, 2, 1e-9}, {0, 2, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 0.0}, {1, 2, 1e-9}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 3,
|
|
{
|
|
{0, 1, 0.0}, {1, 2, 1e-9}, {0, 2, 1.0}
|
|
},
|
|
{
|
|
{0, 1, 0.0}, {1, 2, 1e-9}
|
|
}
|
|
),
|
|
|
|
// Duplicate edges (picks the one with the smallest weight)
|
|
MSTParamType(MST_PRIM, 3,
|
|
{
|
|
{0, 1, 3.0}, {0, 1, 1.0}, {1, 2, 2.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 3,
|
|
{
|
|
{0, 1, 3.0}, {0, 1, 1.0}, {1, 2, 2.0}
|
|
},
|
|
{
|
|
{0, 1, 1.0}, {1, 2, 2.0}
|
|
}
|
|
),
|
|
|
|
// Negative weights
|
|
MSTParamType(MST_PRIM, 3,
|
|
{
|
|
{0, 1, -1.0}, {1, 2, -2.0}, {0, 2, -3.0}
|
|
},
|
|
{
|
|
{1, 2, -2.0}, {0, 2, -3.0}
|
|
}
|
|
),
|
|
|
|
MSTParamType(MST_KRUSKAL, 3,
|
|
{
|
|
{0, 1, -1.0}, {1, 2, -2.0}, {0, 2, -3.0}
|
|
},
|
|
{
|
|
{0, 2, -3.0}, {1, 2, -2.0}
|
|
}
|
|
),
|
|
};
|
|
|
|
inline static std::string MST_name_printer(const testing::TestParamInfo<MST::ParamType>& info)
|
|
{
|
|
std::ostringstream os;
|
|
const auto& algorithm = get<0>(info.param);
|
|
const auto& numNodes = get<1>(info.param);
|
|
const auto& edges = get<2>(info.param);
|
|
const auto& expectedEdges = get<3>(info.param);
|
|
|
|
os << "TestCase_" << info.index << "_";
|
|
switch (algorithm)
|
|
{
|
|
case MST_PRIM: os << "Prim"; break;
|
|
case MST_KRUSKAL: os << "Kruskal"; break;
|
|
default: os << "Unknown algorithm"; break;
|
|
}
|
|
os << "_Nodes_" << numNodes;
|
|
os << "_Edges_" << edges.size();
|
|
os << "_ExpectedEdges_" << expectedEdges.size();
|
|
|
|
return os.str();
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(/**/, MST, testing::ValuesIn(mst_graphs), MST_name_printer);
|
|
|
|
TEST(MSTStress, largeGraph)
|
|
{
|
|
const int numNodes = 100000;
|
|
|
|
std::vector<MSTEdge> edges;
|
|
|
|
for (int i = 0; i < numNodes - 1; ++i)
|
|
edges.push_back({i, i + 1, static_cast<double>(i + 1)});
|
|
|
|
// Add extra edges for complexity
|
|
for (int i = 0; i < numNodes - 10; i += 10)
|
|
edges.push_back({i, i + 10, static_cast<double>(i)});
|
|
for (int i = 0; i + 20 < numNodes; i += 5)
|
|
edges.push_back({i, i + 20, static_cast<double>(i + 1)});
|
|
for (int i = 0; i + 30 < numNodes; i += 3)
|
|
edges.push_back({i, i + 30, static_cast<double>(i % 50 + 1)});
|
|
for (int i = 50; i < numNodes; i += 10)
|
|
edges.push_back({i, i - 25, static_cast<double>(i % 100 + 2)});
|
|
|
|
std::vector<MSTEdge> primMST, kruskalMST;
|
|
bool resultPrim = buildMST(numNodes, edges, primMST, MST_PRIM, 0);
|
|
bool resultKruskal = buildMST(numNodes, edges, kruskalMST, MST_KRUSKAL, 0);
|
|
|
|
EXPECT_TRUE(resultPrim) << "Prim's algorithm failed for large graph ( "
|
|
<< numNodes << " nodes & " << edges.size() << " edges)";
|
|
EXPECT_TRUE(resultKruskal) << "Kruskal's algorithm failed for large graph ( "
|
|
<< numNodes << " nodes & " << edges.size() << " edges)";
|
|
EXPECT_EQ(primMST.size(), static_cast<size_t>(numNodes - 1))
|
|
<< "Prim's MST size incorrect for large graph: expected "
|
|
<< (numNodes - 1) << " edges, got " << primMST.size() << ".";
|
|
EXPECT_EQ(kruskalMST.size(), static_cast<size_t>(numNodes - 1))
|
|
<< "Kruskal's MST size incorrect for large graph: expected "
|
|
<< (numNodes - 1) << " edges, got " << kruskalMST.size() << ".";
|
|
}
|
|
|
|
typedef tuple<int /*numNodes*/,
|
|
std::vector<MSTEdge>/*edges*/
|
|
> MSTNegativeParamType;
|
|
typedef testing::TestWithParam<MSTNegativeParamType> MSTNegative;
|
|
|
|
TEST_P(MSTNegative, disconnectedGraphs)
|
|
{
|
|
const int numNodes = get<0>(GetParam());
|
|
const std::vector<MSTEdge>& edges = get<1>(GetParam());
|
|
|
|
std::vector<MSTEdge> primMST, kruskalMST;
|
|
bool resultPrim = buildMST(numNodes, edges, primMST, MST_PRIM, 0);
|
|
bool resultKruskal = buildMST(numNodes, edges, kruskalMST, MST_KRUSKAL, 0);
|
|
|
|
EXPECT_FALSE(resultPrim) << "Prim's algorithm should fail for disconnected graphs ( "
|
|
<< numNodes << " nodes & " << edges.size() << " edges)";
|
|
EXPECT_FALSE(resultKruskal) << "Kruskal's algorithm should fail for disconnected graphs ( "
|
|
<< numNodes << " nodes & " << edges.size() << " edges)";
|
|
EXPECT_LT(primMST.size(), static_cast<size_t>(numNodes - 1))
|
|
<< "Prim's MST should not contain " << numNodes - 1 << " edges for disconnected graphs";
|
|
EXPECT_LT(kruskalMST.size(), static_cast<size_t>(numNodes - 1))
|
|
<< "Kruskal's MST should not contain " << numNodes - 1 << " edges for disconnected graphs";
|
|
}
|
|
|
|
const MSTNegativeParamType mst_negative[] =
|
|
{
|
|
// Two disconnected subgraphs
|
|
|
|
MSTNegativeParamType(5,
|
|
{
|
|
// Subgraph 1: 0-1
|
|
{0, 1, 1.0},
|
|
// Subgraph 2: 2-3-4
|
|
{2, 3, 2.0}, {3, 4, 3.0}
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(8,
|
|
{
|
|
// Subgraph 1: 0-1-2-3
|
|
{0, 1, 1.0}, {1, 2, 2.0}, {2, 3, 3.0},
|
|
// Subgraph 2: 4-5-6-7
|
|
{4, 5, 4.0}, {5, 6, 5.0}, {6, 7, 6.0}
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(4,
|
|
{
|
|
// Subgraph 1: 0-1
|
|
{0, 1, 1.0},
|
|
// Subgraph 2: 2-3
|
|
{2, 3, 1.0},
|
|
// self-loop edges (should be ignored by the algorithms)
|
|
{0, 0, 1.0}, {1, 1, 1.0}, {2, 2, 1.0}, {3, 3, 1.0}
|
|
}
|
|
),
|
|
|
|
// Three disconnected components
|
|
|
|
MSTNegativeParamType(6,
|
|
{
|
|
// Subgraph 1: 0-1
|
|
{0, 1, 1.0},
|
|
// Subgraph 2: 2-3
|
|
{2, 3, 1.0},
|
|
// Subgraph 3: 4-5
|
|
{4, 5, 1.0}
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(9,
|
|
{
|
|
// Subgraph 1: 0-1-2-3
|
|
{0, 1, 1.0}, {1, 2, 1.0}, {2, 3, 1.0},
|
|
// Subgraph 2: 4-5-6
|
|
{4, 5, 1.0}, {5, 6, 1.0},
|
|
// Subgraph 3: 7-8
|
|
{7, 8, 1.0}
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(8,
|
|
{
|
|
// Subgraph 1: 0-1-2
|
|
{0, 1, 1.0}, {1, 2, 1.0},
|
|
// Subgraph 2: 3-4-5
|
|
{3, 4, 1.0}, {4, 5, 1.0},
|
|
// Subgraph 3: 6-7
|
|
{6, 7, 1.0}
|
|
}
|
|
),
|
|
|
|
// Isolated nodes
|
|
MSTNegativeParamType(4,
|
|
{
|
|
// Subgraph 1: 0-1-2
|
|
{0, 1, 1.0}, {1, 2, 1.0},
|
|
// Isolated node: 3
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(5,
|
|
{
|
|
// Subgraph 1: 0-1-2
|
|
{0, 1, 1.0}, {1, 2, 1.0},
|
|
// Isolated nodes: 3, 4
|
|
}
|
|
),
|
|
|
|
MSTNegativeParamType(3,
|
|
{
|
|
// Isolated node: 0, 1, 2 (no edges)
|
|
}
|
|
),
|
|
};
|
|
|
|
inline static std::string MST_negative_name_printer(const testing::TestParamInfo<MSTNegative::ParamType>& info) {
|
|
std::ostringstream os;
|
|
const auto& numNodes = get<0>(info.param);
|
|
const auto& edges = get<1>(info.param);
|
|
|
|
os << "TestCase_" << info.index << "_Nodes_" << numNodes;
|
|
os << "_Edges_" << edges.size();
|
|
|
|
return os.str();
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(/**/, MSTNegative, testing::ValuesIn(mst_negative), MST_negative_name_printer);
|
|
|
|
}} // namespace
|