1
0
Fork 0

Simulation support for BFS.

This commit is contained in:
Max Neunhoeffer 2016-09-27 10:59:39 +02:00
parent b97005cca3
commit 2913547320
1 changed files with 227 additions and 52 deletions

View File

@ -27,31 +27,31 @@
/// @author Copyright 2016-2016, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var internal = require("internal");
var internal = require('internal');
function makeTree(k, depth, nrShards, subgraph) {
// This creates a large graph (vertices and edges), which is a k-ary
// tree of depth <depth>. If <subgraph> is "random", then the subgraph
// attribute "sub" will be set randomly in [1..nrShards] as string.
// If <subgraph> is ["prefix", d], then the subgraph attribute "sub"
// will be set to "top" for the layers of depth < d and [1..2^d] as
// tree of depth <depth>. If <subgraph> is 'random', then the subgraph
// attribute 'sub' will be set randomly in [1..nrShards] as string.
// If <subgraph> is ['prefix', d], then the subgraph attribute 'sub'
// will be set to 'top' for the layers of depth < d and [1..2^d] as
// string for the 2^d subtrees starting at depth d. If <subgraph> is
// "depth", then the subgraph attribute "sub" will be set to the
// 'depth', then the subgraph attribute 'sub' will be set to the
// depth of the vertex in the tree as string. Returns an object with
// "vertices" and "edges" attribute.
if (typeof k !== "number" || k > 9 || k < 2) {
throw "bad k";
// 'vertices' and 'edges' attribute.
if (typeof k !== 'number' || k > 9 || k < 2) {
throw 'bad k';
}
let r = { "vertices": [], "edges": [], k, depth, nrShards, subgraph };
let r = { 'vertices': [], 'edges': [], k, depth, nrShards, subgraph };
const makeVertices = (which, d) => {
let v = {name: which};
if (subgraph === "random") {
v.sub = "" + Math.floor(Math.random() * nrShards + 1.0);
} else if (subgraph === "depth") {
v.sub = "" + depth;
if (subgraph === 'random') {
v.sub = '' + Math.floor(Math.random() * nrShards + 1.0);
} else if (subgraph === 'depth') {
v.sub = '' + depth;
} else {
if (d < subgraph[1]) {
v.sub = "top";
v.sub = 'top';
} else {
v.sub = which.substr(0, subgraph[1] + 1);
}
@ -63,12 +63,12 @@ function makeTree(k, depth, nrShards, subgraph) {
}
for (let i = 0; i < k; i++) {
let subpos = makeVertices(which + i, d+1);
let e = { _from: pos, _to: subpos, name: "->" + which + i };
let e = { _from: pos, _to: subpos, name: '->' + which + i };
r.edges.push(e);
}
return pos;
};
makeVertices("N", 0);
makeVertices('N', 0);
return r;
}
@ -101,36 +101,36 @@ function makeClusteredGraph(clusterSizes, intDegree, intDegreeDelta,
// seed: random seed
// Just to make the first few cluster names a bit more interesting:
let clusterNames = ["ABC", "ZTG", "JSH", "LIJ", "GTE", "NSD", "POP", "GZU",
"RRR", "WER", "UIH", "KLO", "QWE", "FAD", "MNB", "VFR"];
let clusterNames = ['ABC', 'ZTG', 'JSH', 'LIJ', 'GTE', 'NSD', 'POP', 'GZU',
'RRR', 'WER', 'UIH', 'KLO', 'QWE', 'FAD', 'MNB', 'VFR'];
// Just to make the first 10000 vertex names in a cluster a bit more
// interesting:
let names1 = ["Max", "Ulf", "Andreas", "Kaveh", "Claudius", "Jan",
"Michael", "Oliver", "Frank", "Willi"];
let names2 = ["Friedhelm", "Habakuk", "Mickey", "Karl-Heinz",
"Friedrich-Wilhelm", "Hans-Guenter", "Hades", "Callibso",
"Jupiter", "Odin"];
let names3 = ["Chaplin", "Hardy", "Laurel", "Keaton", "Hallervorden",
"Appelt", "Mittermaier", "Trump", "Queen Elizabeth",
"Merkel"];
let names1 = ['Max', 'Ulf', 'Andreas', 'Kaveh', 'Claudius', 'Jan',
'Michael', 'Oliver', 'Frank', 'Willi'];
let names2 = ['Friedhelm', 'Habakuk', 'Mickey', 'Karl-Heinz',
'Friedrich-Wilhelm', 'Hans-Guenter', 'Hades', 'Callibso',
'Jupiter', 'Odin'];
let names3 = ['Chaplin', 'Hardy', 'Laurel', 'Keaton', 'Hallervorden',
'Appelt', 'Mittermaier', 'Trump', 'Queen Elizabeth',
'Merkel'];
function makeName(i) {
let r = names1[i % 10] + "-" + names2[Math.floor(i / 10) % 10] + "_" +
let r = names1[i % 10] + '-' + names2[Math.floor(i / 10) % 10] + '_' +
names3[Math.floor(i / 100) % 10];
let z = Math.floor(i / 1000);
return z > 0 ? r + z : r;
}
let rand = new PoorMansRandom(seed);
let r = { "vertices": [], "edges": [], clusterSizes,
let r = { 'vertices': [], 'edges': [], clusterSizes,
intDegree, intDegreeDelta, extDegree, seed };
// Make the vertices:
let clusters = [];
for (let i = 0; i < clusterSizes.length; ++i) {
let cluster = [];
let subgraphId = i < clusterNames.length ? clusterNames[i] : "C" + i;
let subgraphId = i < clusterNames.length ? clusterNames[i] : 'C' + i;
for (let j = 0; j < clusterSizes[i]; ++j) {
let v = {subgraphId, name: subgraphId + "_" + makeName(j)};
let v = {subgraphId, name: subgraphId + '_' + makeName(j)};
cluster.push({v, pos: r.vertices.length});
r.vertices.push(v);
}
@ -140,7 +140,7 @@ function makeClusteredGraph(clusterSizes, intDegree, intDegreeDelta,
// Make the edges:
for (let i = 0; i < clusterSizes.length; ++i) {
let cluster = clusters[i];
let subgraphId = i < clusterNames.length ? clusterNames[i] : "C" + i;
let subgraphId = i < clusterNames.length ? clusterNames[i] : 'C' + i;
for (let j = 0; j < cluster.length; ++j) {
let vv = cluster[j];
let v = vv.v;
@ -150,7 +150,7 @@ function makeClusteredGraph(clusterSizes, intDegree, intDegreeDelta,
let toVertexPos = rand.next() % cluster.length;
let toVertex = r.vertices[cluster[toVertexPos].pos];
let e = { _from: vv.pos, _to: cluster[toVertexPos].pos,
name: v.name + "->" + toVertex.name };
name: v.name + '->' + toVertex.name };
r.edges.push(e);
}
if (rand.nextUnitInterval() < extDegree) {
@ -161,13 +161,12 @@ function makeClusteredGraph(clusterSizes, intDegree, intDegreeDelta,
let toVertexPos = rand.next() % clusters[toCluster].length;
let toVertex = r.vertices[clusters[toCluster][toVertexPos].pos];
let e = { _from: vv.pos, _to: clusters[toCluster][toVertexPos].pos,
name: v.name + "->" + toVertex.name };
name: v.name + '->' + toVertex.name };
r.edges.push(e);
}
}
}
console.error("Hugo:", r);
return r;
}
@ -175,17 +174,17 @@ function makeClusteredGraph(clusterSizes, intDegree, intDegreeDelta,
function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
uniqueness, mode, dir) {
// <graph> is an object with attributes "vertices" and "edges", which
// <graph> is an object with attributes 'vertices' and 'edges', which
// simply contain the list of vertices (with their _id attributes)
// and edges (with _id, _from and _to attributes, all strings).
// <startVertexId> is the id of the start vertex as a string,
// <minDepth> (>= 0) and <maxDepth> (>= <minDepth>) are what they
// say, <uniqueness> can be "none", "edgePath", "vertexPath" or
// "vertexGlobal", and <mode> can be "vertex", "edge" or "path", which
// say, <uniqueness> can be 'none', 'edgePath', 'vertexPath' or
// 'vertexGlobal', and <mode> can be 'vertex', 'edge' or 'path', which
// produces output containing the last vertex, the last vertex and
// edge, or the complete path is. <dir> can be "OUT" or "IN" or "ANY"
// edge, or the complete path is. <dir> can be 'OUT' or 'IN' or 'ANY'
// for the direction of travel. The result is an object with an array
// of results (according to <mode>) in the "res" component as well as
// of results (according to <mode>) in the 'res' component as well as
// an array of indices indicating where the depths start.
// First create index tables:
@ -219,17 +218,15 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
break;
}
require("internal").print(pos, graph, schreier);
let id = schreier[pos].vertex._id;
let outEdges = graph.fromTable[id];
let inEdges = graph.toTable[id];
let done = {};
require("internal").print(id, outEdges, inEdges);
let doStep = (edge, vertex) => {
let useThisEdge = true;
// add more checks here depending on uniqueness mode
if (uniqueness === "edgePath") {
if (uniqueness === 'edgePath') {
let p = pos;
while (p !== 0) {
if (schreier[p].edge._id === edge._id) {
@ -238,7 +235,7 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
}
p = schreier[p].pred;
}
} else if (uniqueness === "vertexPath") {
} else if (uniqueness === 'vertexPath') {
let p = pos;
while (p !== null) {
if (schreier[p].vertex._id === vertex._id) {
@ -247,7 +244,7 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
}
p = schreier[p].pred;
}
} else if (uniqueness === "vertexGlobal") {
} else if (uniqueness === 'vertexGlobal') {
if (used[vertex._id] === true) {
useThisEdge = false;
}
@ -257,14 +254,14 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
depth: schreier[pos].depth + 1, pred: pos});
};
if (dir === "OUT" || dir === "ANY") {
if (dir === 'OUT' || dir === 'ANY') {
for (let i = 0; i < outEdges.length; ++i) {
let edge = graph.edges[outEdges[i]];
doStep(edge, graph.table[edge._to]);
done[edge._id] = true;
}
}
if (dir === "IN" || dir === "ANY") {
if (dir === 'IN' || dir === 'ANY') {
for (let i = 0; i < inEdges.length; ++i) {
let edge = graph.edges[inEdges[i]];
if (done[edge._id] === true) {
@ -282,9 +279,9 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
if (schreier[i].depth < minDepth) {
continue;
}
if (mode === "vertex") {
if (mode === 'vertex') {
r.res.push(schreier[i].vertex);
} else if (mode === "edge") {
} else if (mode === 'edge') {
r.res.push({vertex: schreier[i].vertex, edge: schreier[i].edge});
} else {
let pos = i;
@ -305,9 +302,187 @@ function simulateBreadthFirstSearch(graph, startVertexId, minDepth, maxDepth,
return r;
}
function pathCompare(a, b) {
// Establishes a total order on the set of all paths of the form:
// { vertices: [...], edges: [...] }
// where there is one more vertex than edges, and all vertices and
// edges each have an _id attribute uniquely identifying it.
if (a.vertices.length < b.vertices.length) {
return -1;
} else if (a.vertices.length > b.vertices.length) {
return 1;
} else {
let i = 0;
while (true) {
// Will be left by return if i reaches a.vertices.length - 1 at the
// latest.
if (a.vertices[i]._id < b.vertices[i]._id) {
return -1;
} else if (a.vertices[i]._id > b.vertices[i]._id) {
return 1;
} else {
if (i === a.vertices.length - 1) {
return 0; // all is equal
}
if (a.edges[i]._id < b.edges[i]._id) {
return -1;
} else if (a.edges[i]._id > b.edges[i]._id) {
return 1;
}
// A tie so far, move on.
}
i += 1;
}
}
}
function checkBFSResult(pattern, toCheck) {
// This tries to check a BFS result against a given pattern. Some
// fuzziness is needed since AQL does not promise the order in which
// edges are followed from a vertex during the traversal. The mode
// 'vertex', 'edge' or 'path' is detected automatically and refers to
// the three possibilities of the query:
// (1) FOR v IN 0..5 ... OPTIONS {'bfs': true} RETURN v
// (2) FOR v, e IN 0..5 ... OPTIONS {'bfs': true} RETURN {vertex: v, edge: e}
// (3) FOR v, e, p IN 0..5 ... OPTIONS {'bfs': true} RETURN p
// The pattern is of the form
// { 'res': [...], 'depths': [...] }
// with two arrays of equal length as returned by simulateBreadthFirstSearch.
// Further filterings may have been applied to the results.
// The following checks are done:
// (1) Number of results are equal
// (2) The set of vertex IDs in each depth is the same in both results
// (3) The set of edge IDs in each depth is the same in both results
// This function returns an object of the form:
// res = { 'error': true/false, 'errorMessage': '' }
// where errorMessage is set iff error is true. This can be used as in:
// assertFalse(res.error, res.errorMessage)
if (pattern.res.length !== toCheck.length) {
return { error: true, errorMessage: 'Number of results does not match.' };
}
if (pattern.res.length === 0) {
return { error: false };
}
let guck = pattern.res[0];
if (typeof guck !== 'object') {
return { error: true, errorMessage: 'Cannot recognize mode.' };
}
let mode;
if (guck.hasOwnProperty('vertices') && guck.hasOwnProperty('edges')) {
mode = 'path';
} else if (guck.hasOwnProperty('vertex') && guck.hasOwnProperty('edge')) {
mode = 'edge';
} else {
mode = 'vertex';
}
// Not let the show start, first find the depth levels:
let newDepths = [];
let depth = null;
for (let i = 0; i < pattern.depths.length; ++i) {
if (pattern.depths[i] !== depth) {
if (depth !== null && pattern.depths[i] < depth) {
return { error: true,
errorMessage: 'Depths are decreasing.' };
}
depth = pattern.depths[i];
newDepths.push(i);
}
}
newDepths.push(pattern.depths.length);
// Now check one depth after another:
for (let i = 0; i < newDepths.length - 1; ++i) {
if (mode === 'vertex') {
let tab = {};
for (let j = newDepths[i]; j < newDepths[i+1]; ++j) {
tab[pattern.res[j]._id] = true;
}
for (let j = newDepths[i]; j < newDepths[i+1]; ++j) {
if (tab[toCheck[j]._id] !== true) {
return { error: true, errorMessage: 'Ids in depth ' +
pattern.depths[j] + ' do not match, "' +
toCheck[j]._id + '" in toCheck is not in pattern.' };
}
}
} else if (mode === 'edge') {
let vTab = {};
let eTab = {};
for (let j = newDepths[i]; j < newDepths[i+1]; ++j) {
vTab[pattern.res[j].vertex._id] = true;
eTab[pattern.res[j].edge._id] = true;
}
for (let j = newDepths[i]; j < newDepths[i+1]; ++j) {
if (vTab[toCheck[j].vertex._id] !== true) {
return { error: true, errorMessage: 'Vertex ids in depth ' +
pattern.depths[j] + ' do not match, "' +
toCheck[j].vertex._id + '" in toCheck is not in pattern.' };
}
if (eTab[toCheck[j].edge._id] !== true) {
return { error: true, errorMessage: 'Edge ids in depth ' +
pattern.depths[j] + ' do not match, "' +
toCheck[j].edge._id + '" in toCheck is not in pattern.' };
}
}
} else { // mode === 'path'
let colA = [];
let colB = [];
for (let j = newDepths[i]; j < newDepths[i+1]; ++j) {
colA.push(pattern.res[j]);
colB.push(toCheck[j]);
}
colA.sort(pathCompare);
colB.sort(pathCompare);
for (let j = 0; j < colA.length; ++j) {
if (pathCompare(colA[j], colB[j]) !== 0) {
return { error: true, errorMessage: 'Path sets in depth ' +
pattern.depths[newDepths[i]] + ' do not match, here is a ' +
'sample: ' + JSON.stringify(colA) + ' (pattern) as opposed '+
' to ' + JSON.stringify(colB) + ' (toCheck)' };
}
}
}
}
// All done, all is fine:
return { error: false };
}
function storeGraph(r, Vname, Ename, Gname) {
// r a graph made by makeTree or makeClusteredGraph, Vname a string for the
// name of the vertex collection, Ename a string for the name of the edge
// collection, Gname is the name of the named graph, returns an object
// with two attributes "graph" with the general graph object and "data"
// with the object to be used by simulateBreadthFirstSearch.
let db = require('internal').db;
let g = require('@arangodb/general-graph');
db._drop(Vname);
db._drop(Ename);
let V = db._create(Vname);
let E = db._createEdgeCollection(Ename);
let vv = [];
for (let i = 0; i < r.vertices.length; ++i) {
vv.push(V.insert(r.vertices[i]));
}
for (let i = 0; i < r.edges.length; ++i) {
let e = r.edges[i];
e._from = vv[e._from]._id;
e._to = vv[e._to]._id;
E.insert(e);
}
try {
g._drop(Gname);
} catch (err) {
}
let graph = g._create(Gname);
graph._extendEdgeDefinitions(g._relation(Ename, [Vname], [Vname]));
return { data: { vertices: V.toArray(), edges: E.toArray() },
graph };
}
exports.makeTree = makeTree;
exports.PoorMansRandom = PoorMansRandom;
exports.makeClusteredGraph = makeClusteredGraph;
exports.simulateBreadthFirstSearch = simulateBreadthFirstSearch;
exports.checkBFSResult = checkBFSResult;
exports.storeGraph = storeGraph;