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

179 - more graph operations #230

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
31 changes: 30 additions & 1 deletion include/ogdf/basic/graph_generators/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#pragma once

#include <ogdf/basic/Graph.h>
#include <ogdf/basic/NodeArray.h>

namespace ogdf {

Expand Down Expand Up @@ -187,8 +188,36 @@ OGDF_EXPORT void modularProduct(const Graph& G1, const Graph& G2, Graph& product
OGDF_EXPORT void rootedProduct(const Graph& G1, const Graph& G2, Graph& product,
NodeMap& nodeInProduct, node rootInG2);

/**
* Computes the complement of G.
*
* @param G is the input graph, the complement will be assigned to G.
* @param directional Whether directionality should be considered when computing the complement graph. (default is false)
* @param allow_self_loops Whether to allow self loops, if false and G contains self loops, these will not be removed. (default is false)
*/
OGDF_EXPORT void complement(Graph& G, bool directional = false, bool allow_self_loops = false);
/**
* Computes the intersection of G1 and G2. The output will be assigned to G1.
*
* @param G1 is the first graph, the intersection will be assigned to G1.
* @param G2 is the second graph.
* @param nodeMap associates a node in G2 with a node in G1. There should be an entry for every node in G1, this entry may be a nullptr.
*/
OGDF_EXPORT void intersection(Graph& G1, const Graph& G2, const NodeArray<node>& nodeMap);

/**
* Computes the joined graph of G1 and G2. The output will be assigned to G1.
* \f$ (V = V_1 \cup V_2, E = E_1 \cup E_2 \cup V_1 \cross V_2) \f$
* This does not respect parallel edges and the output graph will be parallel free.
*
* @param G1 is the first graph, the joined graph will be assigned to G1.
* @param G2 is the second graph.
* @param nodeMap is assigned a mapping from nodes in \p G2 to new nodes in G1. It should be initialized like this: NodeArray<node> nodeMap(G2);
*/
OGDF_EXPORT void join(Graph& G1, const Graph& G2, NodeArray<node>& nodeMap);


//! @}

/** @} */

}
115 changes: 115 additions & 0 deletions src/ogdf/basic/graph_generators/operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
* http://www.gnu.org/copyleft/gpl.html
*/

#include <ogdf/basic/Graph_d.h>
#include <ogdf/basic/NodeArray.h>
#include <ogdf/basic/NodeSet.h>
#include <ogdf/basic/graph_generators/operations.h>
#include <ogdf/basic/internal/list_templates.h>
#include <ogdf/basic/simple_graph_alg.h>

namespace ogdf {
Expand Down Expand Up @@ -246,4 +250,115 @@ void rootedProduct(const Graph& G1, const Graph& G2, Graph& product, NodeMap& no
});
}

void complement(Graph& G, bool directional, bool allow_self_loops) {
NodeSet<true> n1neighbors(G);
EdgeSet<true> newEdges(G);

safeForEach(G.nodes, [&](node n1) {
// deleting edges
safeForEach(n1->adjEntries, [&](adjEntry adj) {
if (n1->adjEntries.size() <= 0) {
return;
}
node n2 = adj->twinNode();

if (directional && !adj->isSource()) {
return;
}
if (!directional && n1->index() > n2->index()) {
return;
}
if (newEdges.isMember(adj->theEdge())) {
return;
}
n1neighbors.insert(n2);
G.delEdge(adj->theEdge());
});

// adding edges
for (node n2 : G.nodes) {
if (!directional && n1->index() > n2->index()) {
continue;
}
if (!allow_self_loops && n1->index() == n2->index()) {
continue;
}
if (n1neighbors.isMember(n2)) {
continue;
}

edge newEdge = G.newEdge(n1, n2);
newEdges.insert(newEdge);
}
n1neighbors.clear();
});
}

void intersection(Graph& G1, const Graph& G2, const NodeArray<node>& nodeMap) {
OGDF_ASSERT(nodeMap.valid());
NodeSet<true> n2aNeighbors(G2);

safeForEach(G1.nodes, [&](node n1) {
node n2 = nodeMap[n1];
if (n2 == nullptr) {
G1.delNode(n1);
}
});

for (node n1a : G1.nodes) {
node n2a = nodeMap[n1a];
List<edge> edgelist;
n1a->adjEdges(edgelist);

for (adjEntry n2aadj : n2a->adjEntries) {
n2aNeighbors.insert(n2aadj->twinNode());
}
for (edge e1 : edgelist) {
node n1b = e1->opposite(n1a);
node n2b = nodeMap[n1b];

if (!n2aNeighbors.isMember(n2b)) {
G1.delEdge(e1);
}
}
n2aNeighbors.clear();
}
}

void join(Graph& G1, const Graph& G2, NodeArray<node>& mapping) {
OGDF_ASSERT(mapping.valid());
Copy link
Member

Choose a reason for hiding this comment

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

Instead of (or maybe in addition to) valid() I'd rather check mapping.graphOf() == &G2


List<node> G1nodes {};
getAllNodes(G1, G1nodes);

NodeArray<node> nodeMap(G2, nullptr);
EdgeArray<edge> edgeMap(G2, nullptr);
G1.insert(G2, nodeMap, edgeMap);

for (node n2 : G2.nodes) {
node n1_mapped = mapping[n2];
if (n1_mapped == nullptr) {
continue;
}

for (adjEntry adj : n2->adjEntries) {
G1.newEdge(n1_mapped, nodeMap[adj->twinNode()]);
}
node n1_created = nodeMap[n2];
nodeMap[n2] = n1_mapped;
G1.delNode(n1_created);
}

for (node n2 : G2.nodes) {
for (node n1 : G1nodes) {
node n2_in_n1 = nodeMap[n2];
if (n1 != n2_in_n1) {
G1.newEdge(n1, n2_in_n1);
}
}
}

// respecting parallel edges and not accidentally creating some is requires a lot of checks.
makeParallelFreeUndirected(G1);
}
}
106 changes: 106 additions & 0 deletions test/src/basic/operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
#include <ogdf/basic/graph_generators/operations.h>
#include <ogdf/basic/simple_graph_alg.h>

#include <bandit/assertion_frameworks/snowhouse/assert.h>
#include <bandit/assertion_frameworks/snowhouse/constraints/equalsconstraint.h>
#include <bandit/grammar.h>
#include <graphs.h>
#include <testing.h>

Expand Down Expand Up @@ -216,5 +219,108 @@ go_bandit([] {
},
[](int n1, int m1, int n2, int m2) { return m1 + m2 * n1; });
});

describe("tests for creating graph complement", []() {
// Tests for basic functionality
Graph G;
node n1, n2;
before_each([&]() {
G.clear();
n1 = G.newNode();
n2 = G.newNode();
});
describe("tests in a simple graph", [&]() {
it("creates an edge where there was none", [&]() {
complement(G, false, false);
edge edge12 = G.searchEdge(n1, n2);
AssertThat(edge12, Is().Not().Null());
Copy link
Member

Choose a reason for hiding this comment

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

There's also !IsNull() if that is more convenient to you...

});
it("removes an edge where there was one", [&]() {
G.newEdge(n1, n2);
complement(G, false, false);
edge edge12 = G.searchEdge(n1, n2);
AssertThat(edge12, IsNull());
});
});
describe("tests in a directed graph", [&]() {
it("reverses an existing edge", [&]() {
G.newEdge(n1, n2);
complement(G, true, false);
edge edge12 = G.searchEdge(n1, n2, true);
edge edge21 = G.searchEdge(n2, n1, true);
AssertThat(edge12, IsNull());
AssertThat(edge21, Is().Not().Null());
});
it("creates two edges where there were none", [&]() {
complement(G, true, false);
edge edge12 = G.searchEdge(n1, n2, true);
edge edge21 = G.searchEdge(n2, n1, true);
AssertThat(edge12, Is().Not().Null());
AssertThat(edge21, Is().Not().Null());
});
it("removes both edges between two nodes", [&]() {
G.newEdge(n1, n2);
G.newEdge(n2, n1);
complement(G, true, false);
edge edge12 = G.searchEdge(n1, n2, true);
edge edge21 = G.searchEdge(n2, n1, true);
AssertThat(edge12, IsNull());
AssertThat(edge21, IsNull());
});
});
describe("tests in a graph with self loops", [&]() {
it("creates a self loop where there was none", [&]() {
complement(G, false, true);
edge edge11 = G.searchEdge(n1, n1);
AssertThat(edge11, Is().Not().Null());
});
it("removes a self loop where there was one", [&]() {
G.newEdge(n1, n1);
complement(G, false, true);
edge edge11 = G.searchEdge(n1, n1);
AssertThat(edge11, IsNull());
});
});
});
describe("tests for joining two graphs", []() {
Graph G1, G2;
node n1a, n1b, n2a, n2b;
NodeArray<node> nodeMap;
before_each([&]() {
G1.clear();
n1a = G1.newNode();
n1b = G1.newNode();
G2.clear();
n2a = G2.newNode();
n2b = G2.newNode();
nodeMap = NodeArray<node>(G2);
});
it("joins two edgeless graphs without association", [&]() {
join(G1, G2, nodeMap);
AssertThat(G1.numberOfNodes(), Equals(4));
AssertThat(G1.numberOfEdges(), Equals(4));
});
it("joins two edgeless graphs with associated nodes", [&]() {
nodeMap[n2a] = n1a;
join(G1, G2, nodeMap);
AssertThat(G1.numberOfNodes(), Equals(3));
AssertThat(G1.numberOfEdges(), Equals(3));
});
it("joins two graphs without association", [&]() {
G1.newEdge(n1a, n1b);
G2.newEdge(n2a, n2b);
join(G1, G2, nodeMap);
AssertThat(G1.numberOfNodes(), Equals(4));
AssertThat(G1.numberOfEdges(), Equals(6));
});
it("joins two graphs with associated nodes", [&]() {
G1.newEdge(n1a, n1b);
G2.newEdge(n2a, n2b);
nodeMap[n2a] = n1a;
join(G1, G2, nodeMap);
AssertThat(G1.numberOfNodes(), Equals(3));
AssertThat(G1.numberOfEdges(), Equals(3));
});
});
});
});
Loading