Skip to content

Commit

Permalink
Implemented maximum bipartite matching algorithm and written unit tes…
Browse files Browse the repository at this point in the history
…ts for it
  • Loading branch information
rowanG077 committed Nov 12, 2018
1 parent 104db9d commit 87a475b
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 38 deletions.
13 changes: 13 additions & 0 deletions cmake/code.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
endif()
endif()

# set optimization flags for release
if(CMAKE_BUILD_TYPE STREQUAL "Release")
if(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR
CMAKE_C_COMPILER_ID STREQUAL "GNU")
add_compile_options(-O3)
if(${WERROR})
add_compile_options(-Werror)
endif()
else()
message(WARNING "Optimization not supported for current compiler.")
endif()
endif()

include_directories(BEFORE include)

if(${COVERAGE})
Expand Down
37 changes: 21 additions & 16 deletions include/BoxNesting/Algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,53 @@ class Algorithm
* of visible boxes
*
* @param boxes the set of boxes to be nested
* @return uint64_t the number of visible boxes after running the nesting
* @return the number of visible boxes after running the nesting
* algorithm
*/
[[nodiscard]] uint16_t runAlgorithm(const std::vector<Box>& boxes) const;

/**
* @brief function that appends data to an empty graph given a set of boxes.
* Every box represents a
* vertex in the graph where an edge is appended from vertex a to b if the box
* representing vertex a
* nests inside the box representing vertex b
* @brief function creates a bipartite graph from a vector of boxes
* It will create two vertices for each box one in the left
* and one in the right part of the graph
* Then it will create edges between the left and the right part
* of the graph if the box in the left vertex can nest inside
* the box on the right vertex
*
* @param boxes the set of boxes that will be used to create the graph
*
* @return the graph of type BoxNesting::Box
* @return The constructed BipartiteGraph
*/
[[nodiscard]] Graph::BipartiteGraph<Box> createGraphFromBoxes(const std::vector<Box>& boxes) const;

private:
/**
* @brief Run the kuhn algorithm
* @brief Run the kuhn algorithm. It's based on the DFS and will try to find
* chains of maximum length.
*
* @param graph The total graph
* @param vertex The index of the vertex in the graph its neighbours
* @param used Contains whether a vertex in the left part of the graph was used
* @param pairs Contains which pairs are allready made
* @param vertex The index of the vertex in the left part of the
* Bipartite graph to start running the algorithm from
*
* @return Whether pairs could be made
* @return Whether a chain could be made
*/
bool kuhn(uint16_t vertex) const;

private:
/**
* @brief Graph generated by the boxes
* @brief Graph generated from the boxes
*/
mutable Graph::BipartiteGraph<Box> graph;

/**
* @brief Contains
* @brief Contains whether a vertex was allready explored during the current kuhn run
*/
mutable std::vector<bool> used;
mutable std::vector<int32_t> pairs;

/**
* @brief Contains whether vertices on the right side of the bipartite graph are part
* of a chain
*/
mutable std::vector<uint16_t> pairs;
};

} // namespace BoxNesting
20 changes: 8 additions & 12 deletions include/Graph/AdjacencyMatrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ namespace Graph
template<class T> class AdjacencyMatrix
{
public:
/**
* @brief Construct a new empty AdjacencyMatrix object
*/
AdjacencyMatrix() : data(0)
{

}
/**
* @brief Construct a new empty AdjacencyMatrix object
*/
AdjacencyMatrix() : data(0)
{
}

/**
* @brief Construct a new AdjacencyMatrix object
Expand All @@ -32,7 +31,6 @@ template<class T> class AdjacencyMatrix
*/
AdjacencyMatrix(size_t l, size_t r) : data(l * r), leftCount(l), rightCount(r)
{

}

/**
Expand Down Expand Up @@ -60,11 +58,9 @@ template<class T> class AdjacencyMatrix
*
* @return Const value at that index
*/
[[nodiscard]] const T& at(size_t l, size_t r) const {
return this->data.at(l * this->rightCount + r);
}
[[nodiscard]] const T& at(size_t l, size_t r) const { return this->data.at(l * this->rightCount + r); }

private:
private:
/**
* @brief Contains the data of the adjacencyMatrix
*/
Expand Down
2 changes: 1 addition & 1 deletion include/Graph/BipartiteGraph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ template<class T> class BipartiteGraph
{
public:
/**
* @brief Construct a new empty bipartite graph
* @brief Construct a new empty bipartite graph
*/
BipartiteGraph() = default;

Expand Down
34 changes: 27 additions & 7 deletions src/Algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ uint16_t Algorithm::runAlgorithm(const std::vector<Box>& boxes) const
{
this->graph = createGraphFromBoxes(boxes);

// n is the amount of vertices in the left part and the right part
// of the bipartite graph. and k the amount of vertices in the right part
// of the graph. They are the same in our box case but could be different
// n is the amount of vertices in the left part of the bipartite graph
// k the amount of vertices in the right part of the bipartite graph
// They are the same in our boxes case but could be different
// in other cases

// since we know graph size doesn't exceed uint16_t we can cast here safely
auto n = static_cast<uint16_t>(graph.getLeftVertices().size());
auto k = static_cast<uint16_t>(graph.getRightVertices().size());

this->pairs = std::vector<int32_t>(n, -1);
// We use 0 for no pair and the index of the vertex + 1 for found pair
this->pairs = std::vector<uint16_t>(n, 0);
this->used = std::vector<bool>(k, false);

// iterate through vertices of the left side
Expand All @@ -31,7 +32,16 @@ uint16_t Algorithm::runAlgorithm(const std::vector<Box>& boxes) const
kuhn(i);
}

return 0;
uint16_t matches = 0;
for (uint16_t i = 0; i < k; ++i) {
if (pairs[i] != 0) {
++matches;
}
}

// amount of visible boxes is the total amount of boxes
// minus the matches. Since a match means a box can nest inside another box
return static_cast<uint16_t>(boxes.size() - matches);
}

bool Algorithm::kuhn(uint16_t vertex) const
Expand All @@ -40,9 +50,19 @@ bool Algorithm::kuhn(uint16_t vertex) const
return false;
}
this->used[vertex] = true;
this->pairs[vertex] = static_cast<int32_t>(graph.getLeftVertices().size());

return true;
auto sz = this->graph.getRightVertices().size();
for (uint16_t i = 0; i < sz; ++i) {
if (!this->graph.isEdgeBetween(vertex, i)) {
continue;
}
// Correct for the NILL value by removing/adding one to convert to vertex index
if (pairs[i] == 0 || kuhn(static_cast<uint16_t>(pairs[i] - 1))) {
pairs[i] = static_cast<uint16_t>(vertex + 1);
return true;
}
}
return false;
}

Graph::BipartiteGraph<Box> Algorithm::createGraphFromBoxes(const std::vector<Box>& boxes) const
Expand Down
65 changes: 63 additions & 2 deletions test/Algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

SCENARIO("Running the 'graph creation from a set of boxes' function")
{
BoxNesting::Algorithm boxNestingAlgorithm;

GIVEN("A set of boxes")
{
std::vector<BoxNesting::Box> boxes{BoxNesting::Box({0.6f, 0.6f, 0.6f}), BoxNesting::Box({0.7f, 0.7f, 0.7f}),
BoxNesting::Box({0.8f, 0.8f, 0.8f})};

BoxNesting::Algorithm boxNestingAlgorithm;

WHEN("Creating a graph from the set of boxes")
{
const auto graph = boxNestingAlgorithm.createGraphFromBoxes(boxes);
Expand All @@ -36,4 +36,65 @@ SCENARIO("Running the 'graph creation from a set of boxes' function")
}
}
}

GIVEN("A set of boxes where every box except for one can nest inside another")
{
std::vector<BoxNesting::Box> boxes{BoxNesting::Box({0.9f, 0.9f, 0.9f}), BoxNesting::Box({0.8f, 0.8f, 0.8f}),
BoxNesting::Box({0.7f, 0.7f, 0.7f}), BoxNesting::Box({0.6f, 0.6f, 0.6f})};

THEN("Only one visible box remains after nesting")
{
auto result = boxNestingAlgorithm.runAlgorithm(boxes);
REQUIRE(result == 1);
}
}

GIVEN("A set of boxes where only one box can nest inside another")
{
std::vector<BoxNesting::Box> boxes1{BoxNesting::Box({0.6f, 0.6f, 0.6f}), BoxNesting::Box({0.75f, 0.75f, 0.75f}),
BoxNesting::Box({0.9f, 0.7f, 0.7f})};

std::vector<BoxNesting::Box> boxes2{BoxNesting::Box({0.75f, 0.75f, 0.75f}),
BoxNesting::Box({0.80f, 0.70f, 0.74f}), BoxNesting::Box({0.73f, 0.65f, 0.85f}),
BoxNesting::Box({0.60f, 0.90f, 0.72f}), BoxNesting::Box({0.95f, 0.55f, 0.71f}),
BoxNesting::Box({0.52f, 0.98f, 0.70f}), BoxNesting::Box({0.70f, 0.75f, 0.69f})};

THEN("One less box is visible after nesting")
{
auto result1 = boxNestingAlgorithm.runAlgorithm(boxes1);
REQUIRE(result1 == (boxes1.size() - 1));
auto result2 = boxNestingAlgorithm.runAlgorithm(boxes2);
REQUIRE(result2 == (boxes2.size() - 1));
}
}

GIVEN("A set of 5000 boxes which can all nest eachother except for one")
{
std::vector<BoxNesting::Box> boxes;
for (uint16_t i = 0; i < 5000; ++i) {
auto length = 0.50001f + (0.00009f * i);
boxes.emplace_back(BoxNesting::Box({length, length, length}));
}

THEN("Only one visible box remains after nesting")
{
auto result = boxNestingAlgorithm.runAlgorithm(boxes);
REQUIRE(result == 1);
}
}

GIVEN("A set of 5000 boxes where none can nest inside eachother")
{
std::vector<BoxNesting::Box> boxes;
for (uint16_t i = 0; i < 5000; ++i) {
auto length = 0.50001f;
boxes.emplace_back(BoxNesting::Box({length, length, length}));
}

THEN("Only one visible box remains after nesting")
{
auto result = boxNestingAlgorithm.runAlgorithm(boxes);
REQUIRE(result == boxes.size());
}
}
}

0 comments on commit 87a475b

Please sign in to comment.