Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add b-Suitor algorithm and b-Matching classes #1109

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

friedagerharz
Copy link
Collaborator

@friedagerharz friedagerharz commented Aug 7, 2023

Implementation of the sequential b-Suitor algorithm by Khan et al. and required b-matching classes.

  • BMatching.hpp represents basic properties of a b-matching data structure. It has a similar functionality as the Matching.hpp class. (They could be merged or inherit as 1-matching from b-matching if the performance remains stable.)
  • BMatcher.hpp is the abstract base class for b-matching algorithms.
  • BSuitorMatcher.hpp adds the sequential b-Suitor with a b-function. It can be used to compute a b-matching for a given undirected weighted graph and any b. E.g.:
auto G = METISGraphReader{}.read("input/tiny_02.graph");
const int b = 2;
BSuitorMatcher bsm(G, b);
bsm.run();
const auto M = bsm.getBMatching();

computes the 2-matching for the tiny_02 graph

3 2  2 1
1 1 
1 2  5 3 
7 5  6 2 
3 3 
4 2  7 6 
4 5  6 6

in (vertex_neighbor,edge_weight) METIS-like format with a weight of 19.

@friedagerharz friedagerharz force-pushed the frieda/b-suitor branch 3 times, most recently from 1d26d92 to bccb01f Compare August 10, 2023 14:48
@friedagerharz friedagerharz force-pushed the frieda/b-suitor branch 4 times, most recently from f8d17a9 to c7fde94 Compare August 23, 2023 22:10
@friedagerharz friedagerharz force-pushed the frieda/b-suitor branch 2 times, most recently from d342c99 to b993e0e Compare September 15, 2023 00:23
@friedagerharz friedagerharz force-pushed the frieda/b-suitor branch 4 times, most recently from 7e34f0b to 18f97b0 Compare September 25, 2023 08:17
@friedagerharz friedagerharz mentioned this pull request Sep 25, 2023
1 task
@friedagerharz friedagerharz marked this pull request as ready for review September 25, 2023 10:52
@friedagerharz friedagerharz force-pushed the frieda/b-suitor branch 4 times, most recently from aae6c0c to c66a1fe Compare September 29, 2023 11:36
for (node v : G.nodeRange()) {
for (index i = 0; i < b.at(i); i++) {
node w = data[v][i];
if ((v != w) && (w != none) && !G.hasEdge(v, w)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't self loops allowed in b-matchings (with b>1)? Also, this can be tested in one loop with the one above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, undirected simple graphs are considered.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged the loops in 14494fe.

for (node v : G.nodeRange()) {
for (index i = 0; i < b.at(i); i++) {
node w = data[v][i];
if (w != none && std::find(data[w].begin(), data[w].end(), v) == data[w].end()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for a none matching? If its happening because of #Neighbors(u) < b, maybe you can escape the inner loop early.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I restructured the BMatching class in 14494fe. Now, there are no more none nodes in the matching and a range based loop is used.

namespace NetworKit {

BMatching::BMatching(const std::vector<count> &b, count z)
: data(z, std::vector<node>(b.size(), none)), b(b) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization of data looks wrong (b.size() likely equals number of nodes, not number of matchings per node).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhhh yes, thank you. I chaned this to b.at(i) in some other branch but I completely forgot to cherry pick this commit. Added it now.

networkit/cpp/matching/test/MatcherGTest.cpp Show resolved Hide resolved
if (self < v.id)
weight += v.weight;
if (size == max_size && !list.empty()) {
min = *std::min_element(list.begin(), list.end(), [](const Node &a, const Node &b) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be consistent concerning lambda function parameter names. This makes the code more readable. Also b is already used for the node variable matching indicators. Better not reuse it in another related context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change some names in cb0aa0e including this one.

data[u][i] = v;
}
{
auto i = findFirstFreeIndex(v);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of std::find done in the code. Even though b is likely to be small, have you checked whether std::set (or a struct based on it) is a faster solution?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restructured BMatching in 14494fe. Used std::set instead.

networkit/cpp/matching/BSuitorMatcher.cpp Show resolved Hide resolved
std::vector<count> getB() const;

protected:
std::vector<std::vector<node>> data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using a more precise name for class member variables.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 14494fe.

void buildBMatching();

private:
std::vector<std::unique_ptr<Suitors>> S;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest not using single char names for class member variables.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in cb0aa0e.

include/networkit/matching/BSuitorMatcher.hpp Show resolved Hide resolved
Copy link
Member

@angriman angriman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I left some comments.

Comment on lines +81 to +82
const std::vector<std::set<node>> &getMatches() const;
std::vector<count> getB() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are public functions, please add proper documentation.

std::vector<count> getB() const;

protected:
std::vector<std::set<node>> matches;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to keep the matches sorted? If not, I think we could use a std::unordered_set.


namespace NetworKit {

struct Node {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This defines a NetworKit::Node struct. I think that this name is too generic for a struct used only by this class. I suggest using something more specific, e.g., WeightedNode, or moving these structs into the BSuitorMatcher.

if (partners.size() < max_size) {
return {none, 0};
} else {
auto ret = min;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More readable:

Suggested change
auto ret = min;
Node min_copy = min;

Comment on lines +67 to +70
if (v.id == u) {
return true;
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (v.id == u) {
return true;
}
return false;
return v.id == u;


for (auto n : G->weightNeighborRange(u)) {
const Node v = Node(n.first, n.second);
if (!hasProposedTo(v.id)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can save an indentation level with:

Suggested change
if (!hasProposedTo(v.id)) {
if (hasProposedTo(v.id)) continue;

const Node v = Node(n.first, n.second);
if (!hasProposedTo(v.id)) {
if (v.weight > best.weight || (v.weight == best.weight && v.id < best.id)) {
const auto n_suitor_weight = Suitors.at(v.id)->min.weight;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const auto n_suitor_weight = Suitors.at(v.id)->min.weight;
const edgeweight n_suitor_weight = Suitors.at(v.id)->min.weight;

Comment on lines +119 to +120
G->forNodes([&](node u) {
G->forNodes([&](node v) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can stop iterating once sym is false. I suggest replacing these two loops with two for (node u : G->nodeRange()) loops and break once sym is false.

// TODO make parallel
G->forNodes([&](node x) {
assert(Suitors.at(x)->partners.size() <= b.at(x));
for (auto y : Suitors.at(x)->partners) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (auto y : Suitors.at(x)->partners) {
for (node y : Suitors.at(x)->partners) {

*/
void run() override;

void buildBMatching();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants