mirror of https://gitee.com/bigwinds/arangodb
Simulation support for BFS.
This commit is contained in:
parent
b97005cca3
commit
2913547320
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue