UNPKG

gramoloss

Version:

Graph theory package for edition and computation

1,383 lines (1,382 loc) 127 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Graph = exports.DominationVariant = void 0; const link_1 = require("./link"); const vertex_1 = require("./vertex"); const utils_1 = require("./utils"); const dfvs_1 = require("./algorithms/dfvs"); const cycle_1 = require("./algorithms/cycle"); const isTournamentLight_1 = require("./algorithms/isTournamentLight"); const dichromatic_1 = require("./algorithms/dichromatic"); /** * For the Dominating Set Algo * @todo should be removed by implementing a method for IDS */ var DominationVariant; (function (DominationVariant) { DominationVariant[DominationVariant["Independent"] = 0] = "Independent"; DominationVariant[DominationVariant["OrientedIndependent"] = 1] = "OrientedIndependent"; })(DominationVariant || (exports.DominationVariant = DominationVariant = {})); class Graph { constructor(n) { this.n = 0; this.linksCounter = 0; this.vertices = new Map(); this.links = new Map(); this.matrix = new Array(); if (typeof n != "undefined") { for (let i = 0; i < n; i++) { const angle = i * 2 * 3.14 / n; this.addVertex(i, Math.cos(angle), Math.sin(angle)); } } } nbVertices() { return this.n; } nbEdges() { let c = 0; for (const link of this.links.values()) { if (link.orientation == link_1.ORIENTATION.UNDIRECTED) { c += 1; } } return c; } nbArcs() { let c = 0; for (const link of this.links.values()) { if (link.orientation == link_1.ORIENTATION.DIRECTED) { c += 1; } } return c; } getAvailableVertexIndex() { let c = this.vertices.size; while (this.vertices.has(c)) { c += 1; } return c; } /** * Add a vertex to the graph. */ addVertex(index, x, y) { const vx = x !== null && x !== void 0 ? x : 0; const vy = y !== null && y !== void 0 ? y : 0; const newVertex = new vertex_1.Vertex(this, index, this.n, vx, vy); this.vertices.set(index, newVertex); this.n += 1; this.matrix.push(new Array(this.n).fill(0)); for (let i = 0; i < this.n - 1; i++) { this.matrix[i].push(0); } return newVertex; } deleteVertex(v) { // Update the neighbors, in-neighbors and out-neighbors for (const [linkIndex, link] of v.incidentLinks) { const u = link.endVertex; if (link.startVertex.index == v.index) { if (link.orientation == link_1.ORIENTATION.UNDIRECTED) { u.incidentLinks.delete(linkIndex); u.neighbors.delete(v.index); } else { u.incidentLinks.delete(linkIndex); u.inNeighbors.delete(v.index); } } else if (link.endVertex.index == v.index) { if (link.orientation == link_1.ORIENTATION.UNDIRECTED) { u.incidentLinks.delete(linkIndex); u.neighbors.delete(v.index); } else { u.incidentLinks.delete(linkIndex); u.outNeighbors.delete(v.index); } } this.links.delete(linkIndex); } // Update the matrix v.stackedIndex; this.matrix.splice(v.stackedIndex, 1); this.n -= 1; for (let i = 0; i < this.n; i++) { this.matrix[i].splice(v.stackedIndex, 1); } this.vertices.delete(v.index); } addEdge(u, v) { const source = typeof u === "number" || typeof u === "string" ? this.vertices.get(u) : u; if (typeof source == "undefined") { return new Error(`Vertex with id ${u} not found`); } const target = typeof v === "number" || typeof v === "string" ? this.vertices.get(v) : v; if (typeof target == "undefined") { return new Error(`Vertex with id ${v} not found`); } const index = this.linksCounter; const newLink = new link_1.Link(index, this, source, target, link_1.ORIENTATION.UNDIRECTED); this.links.set(index, newLink); this.linksCounter += 1; this.matrix[source.stackedIndex][target.stackedIndex] += 1; this.matrix[target.stackedIndex][source.stackedIndex] += 1; if (source.neighbors.has(target.index) == false) { source.neighbors.set(target.index, target); } if (target.neighbors.has(source.index) == false) { target.neighbors.set(source.index, source); } return newLink; } hasEdge(a, b) { const u = (a instanceof vertex_1.Vertex) ? a : this.vertices.get(a); const v = (b instanceof vertex_1.Vertex) ? b : this.vertices.get(b); if (typeof u == "undefined") { throw Error(`No vertex with index ${a} found in graph`); } if (typeof v == "undefined") { throw Error(`No vertex with index ${b} found in graph`); } return u.neighbors.has(v.index); } /** * Delete the edge between u and v if any. * If the graph is not simple, there may be several edges between u and v. * Therefore all the edges between u and v are deleted. */ deleteEdge(u, v) { if (u.neighbors.has(v.index)) { u.neighbors.delete(v.index); v.neighbors.delete(u.index); this.matrix[u.stackedIndex][v.stackedIndex] = 0; this.matrix[v.stackedIndex][u.stackedIndex] = 0; for (const [linkIndex, link] of u.incidentLinks) { if (link.orientation == link_1.ORIENTATION.UNDIRECTED && (link.startVertex.index == v.index || link.endVertex.index == v.index)) { u.incidentLinks.delete(linkIndex); v.incidentLinks.delete(linkIndex); this.links.delete(linkIndex); } } } } addArc(u, v) { const source = typeof u === "number" || typeof u === "string" ? this.vertices.get(u) : u; if (typeof source == "undefined") { return new Error(`Vertex with id ${u} not found`); } const target = typeof v === "number" || typeof v === "string" ? this.vertices.get(v) : v; if (typeof target == "undefined") { return new Error(`Vertex with id ${v} not found`); } const index = this.linksCounter; const newLink = new link_1.Link(index, this, source, target, link_1.ORIENTATION.DIRECTED); this.links.set(index, newLink); this.linksCounter += 1; this.matrix[source.stackedIndex][target.stackedIndex] += 1; if (source.outNeighbors.has(target.index) == false) { source.outNeighbors.set(target.index, target); } if (target.inNeighbors.has(source.index) == false) { target.inNeighbors.set(source.index, source); } return newLink; } hasArc(a, b) { const u = (a instanceof vertex_1.Vertex) ? a : this.vertices.get(a); const v = (b instanceof vertex_1.Vertex) ? b : this.vertices.get(b); if (typeof u == "undefined") { throw Error(`No vertex with index ${a} found in graph`); } if (typeof v == "undefined") { throw Error(`No vertex with index ${b} found in graph`); } return u.outNeighbors.has(v.index); } degree(v) { if (v instanceof vertex_1.Vertex) { return v.degree(); } else { const u = this.vertices.get(v); if (typeof u != "undefined") { return u.degree(); } else { throw Error(`No vertex with index ${v} found in the graph`); } } } indegree(v) { if (v instanceof vertex_1.Vertex) { return v.indegree(); } else { const u = this.vertices.get(v); if (typeof u != "undefined") { return u.indegree(); } else { throw Error(`No vertex with index ${v} found in the graph`); } } } outdegree(v) { if (v instanceof vertex_1.Vertex) { return v.outdegree(); } else { const u = this.vertices.get(v); if (typeof u != "undefined") { return u.outdegree(); } else { throw Error(`No vertex with index ${v} found in the graph`); } } } // TODO : delete arc // PRINTERS print() { console.log("-----------"); console.log("adjacencies"); for (const v of this.vertices.values()) { if (v.neighbors.size > 0) { console.log(`${v.index} -- `, new Set(v.neighbors.keys())); } if (v.outNeighbors.size > 0) { console.log(`${v.index} -> `, new Set(v.outNeighbors.keys())); } if (v.inNeighbors.size > 0) { console.log(`${v.index} <- `, new Set(v.inNeighbors.keys())); } } console.log("matrix"); const matrixFormatted = this.matrix.map(row => row.join(' ')).join('\n'); console.log(matrixFormatted); } /** * @todo TRANSPOSED * Only print arcs */ printDirectedAdjacencyMatrix() { function boolToNum(b) { return b ? b : 0; } const matrix01 = this.matrix.map(row => row.map(boolToNum)); const matrixFormatted = matrix01.map(row => row.join(' ')).join('\n'); console.log(matrixFormatted); } /** * Returns an undirected Graph given by its edges. * @param edgesList */ static fromEdges(edgesList, verticesPositions) { const g = new Graph(); for (const [x, y] of edgesList) { if (g.vertices.has(x) == false) { g.addVertex(x); } if (g.vertices.has(y) == false) { g.addVertex(y); } } if (typeof verticesPositions != "undefined") { for (let i = 0; i < verticesPositions.length; i++) { const v = g.vertices.get(i); if (typeof v != "undefined") { v.pos.x = verticesPositions[i][0]; v.pos.y = verticesPositions[i][1]; } } } for (const [indexV1, indexV2, w] of edgesList) { const edge = g.addEdge(indexV1, indexV2); if (typeof w != "undefined" && edge instanceof link_1.Link) { edge.weight = w; } } return g; } /** * Returns an undirected Graph given by its edges. * @param arcs */ static fromArcs(arcs, verticesPositions) { const g = new Graph(); for (const [x, y] of arcs) { if (g.vertices.has(x) == false) { g.addVertex(x); } if (g.vertices.has(y) == false) { g.addVertex(y); } } if (typeof verticesPositions != "undefined") { for (let i = 0; i < verticesPositions.length; i++) { const v = g.vertices.get(i); if (typeof v != "undefined") { v.pos.x = verticesPositions[i][0]; v.pos.y = verticesPositions[i][1]; } } } for (const [indexV1, indexV2, w] of arcs) { const arc = g.addArc(indexV1, indexV2); if (typeof w != "undefined" && arc instanceof link_1.Link) { arc.weight = w; } } return g; } /** * @returns Petersen graph (10 vertices, 15 edges) * @see https://en.wikipedia.org/wiki/Petersen_graph */ static petersen() { return Graph.fromEdges([[0, 1], [1, 2], [2, 3], [3, 4], [4, 0], [5, 6], [6, 7], [7, 8], [8, 9], [9, 5], [0, 5], [1, 7], [2, 9], [3, 6], [4, 8]]); } /** * Returns a random graph with n vertices. * @param n number of vertices. Should be >= 0. Return empty graph if n < 1. * @param p probabilty of appearance of an edge. Should be between 0 and 1. */ static randomGNP(n, p) { const g = new Graph(n); if (n < 1) { return g; } for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { if (Math.random() < p) { g.addEdge(i, j); } } } return g; } /** * Returns a graph with vertex set indices [0, sum(sizes)-1] * Vi = sum( sizes[k], k < i) + [0, sizes[i]-1] * For every i and j, every vertex of Vi is adjacent to every vertex of Vj * @param sizes list of the sizes of the parts * @example * For sizes = [5,4,3], the graph has 5+4+3 vertices * The sum of the degrees is 5*(4+3) + 4*(5+3) + 3*(5+4). */ static completeMultipartite(sizes) { const graph = new Graph(); const k = sizes.length; const r = 50; let counter = 0; for (let i = 0; i < k; i++) { for (let ki = 0; ki < sizes[i]; ki++) { graph.addVertex(counter, r * Math.cos((2 * Math.PI * i) / k) + (-Math.sin((2 * Math.PI * i) / k)) * (ki - sizes[i] / 2), r * Math.sin((2 * Math.PI * i) / k) + (Math.cos((2 * Math.PI * i) / k)) * (ki - sizes[i] / 2)); counter += 1; } } let sumi = 0; for (let i = 0; i < k; i++) { let sumj = 0; for (let j = 0; j < i; j++) { for (let ki = 0; ki < sizes[i]; ki++) { for (let kj = 0; kj < sizes[j]; kj++) { graph.addArc(sumi + ki, sumj + kj); } } sumj += sizes[j]; } sumi += sizes[i]; } return graph; } /** * Special case of `completeMultipartite` * @param n * @param m * @returns */ static completeBipartite(n, m) { return this.completeMultipartite([n, m]); } /** * Returns a random oriented graph with n vertices. * @param n number of vertices. Should be >= 0. Return empty graph if n < 1. * @param p probabilty of appearance of an edge. Should be between 0 and 1. */ static randomOGNP(n, p) { const g = new Graph(n); if (n < 1) { return g; } for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { if (Math.random() < p) { if (Math.random() < 0.5) { g.addArc(i, j); } else { g.addArc(j, i); } } } } return g; } /** * An acyclic tournament is a tournament which contains no directed cycle. * Such a graph is a Directed Acyclic Graph (DAG). * @param n number of vertices */ static acyclicTournament(n) { return Graph.UGtournament(n, 0); } /** * for every i < j, i -> j iff i+j is prime * @param n */ static testTournament(n) { const graph = new Graph(n); for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { if ((0, utils_1.isPrime)(i + j)) { graph.addArc(j, i); } else { graph.addArc(i, j); } } } return graph; } static generateAztecDiamond(n) { const graph = new Graph(); function check(i, j, n) { return (i + j >= n - 1 && i + j <= 3 * n + 1 && j - i <= n + 1 && i - j <= n + 1); } const indices = new Array(); let counter = 0; for (let i = 0; i < 2 * n + 1; i++) { indices.push(new Array()); for (let j = 0; j < 2 * n + 1; j++) { indices[i].push(-1); if (check(i, j, n)) { const v = graph.addVertex(counter, i * 30 - n * 30, j * 30 - n * 30); indices[i][j] = v.index; counter += 1; } } } for (let i = 0; i < 2 * n + 1; i++) { for (let j = 0; j < 2 * n + 1; j++) { if (indices[i][j] != -1) { if (check(i + 1, j, n) && i + 1 < 2 * n + 1) { graph.addArc(indices[i][j], indices[i + 1][j]); } if (check(i, j + 1, n) && j + 1 < 2 * n + 1) { graph.addArc(indices[i][j], indices[i][j + 1]); } } } } return graph; } static generateGrid(n, m) { const graph = new Graph(); let counter = 0; for (let i = 0; i < n; i++) { for (let j = 0; j < m; j++) { graph.addVertex(counter, i * 30, j * 30); counter += 1; } } for (let i = 0; i < n; i++) { for (let j = 0; j < m; j++) { let current_index = i * m + j; if (j < m - 1) { graph.addArc(current_index, current_index + 1); } if (i < n - 1) { graph.addArc(current_index, current_index + m); } } } return graph; } /** * @returns an undirected cycle of length `n`. * * This graph has n vertices and n edges. */ static cycle(n) { const g = Graph.path(n); g.addEdge(n - 1, 0); return g; } static clique(n) { const g = new Graph(n); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { g.addEdge(i, j); } } return g; } /** * @param n is the number of vertices */ static path(n) { const g = new Graph(n); for (let i = 0; i < n; i++) { if (i > 0) { g.addEdge(i - 1, i); } } return g; } /** * @param n is the number of vertices * @returns an oriented path (`n` vertices and `n-1` edges) */ static orientedPath(n) { const g = new Graph(n); for (let i = 0; i < n; i++) { if (i > 0) { g.addArc(i - 1, i); } } return g; } /** * @param n is the number of vertices * @returns an oriented cycle (`n` vertices and `n` edges) */ static orientedCycle(n) { const g = new Graph(); const vertices = new Array(); for (let i = 0; i < n; i++) { const v = g.addVertex(0, 0); if (i > 0) { g.addArc(vertices[vertices.length - 1], v); } vertices.push(v); } g.addArc(vertices[vertices.length - 1], vertices[0]); return g; } /** * Tournarment with n vertices such that v(i) -> v(i-j) for all j in [1,k] * @param n number of vertices * @param k order * @example k = 0: acyclic * @example k = 1: U-tournaments */ static UGtournament(n, k) { const graph = new Graph(n); for (let i = 0; i < n; i++) { for (let j = 1; j <= k; j++) { if (i - j >= 0) { graph.addArc(i, i - j); } } for (let j = 0; j < i - k; j++) { graph.addArc(j, i); } } return graph; } /** * Return a random Unit Disk graph where vertices are set uniformely randomly in [-50,50]^2. * @param n integer >= 0, the number of vertices * @param d maximum distance between adjacent vertiecs */ static generateUnitDisk(n, d) { const graph = new Graph(); const vertices = new Array(); for (let i = 0; i < n; i++) { vertices.push(graph.addVertex(i, Math.random() * 100 - 50, Math.random() * 100 - 50)); } for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const dist = vertices[i].distTo(vertices[j]); if (dist < d) { graph.addArc(i, j); } } } return graph; } static randomTournament(n) { const graph = new Graph(n); for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { if (Math.random() < 0.5) { graph.addArc(j, i); } else { graph.addArc(i, j); } } } return graph; } /** * @param n * @returns Star with n+1 vertices and n edges */ static star(n) { const graph = new Graph(n + 1); const r = 50; if (n > 0) { for (let i = 1; i <= n; i++) { graph.addArc(0, i); } } return graph; } static generateIndependentCircle(n) { return new Graph(n); } /** * With Markov Chain strategy * @param n number of vertices * @returns a tree */ static randomTree(n) { const root = Math.floor(n / 2); const leaves = [0, n - 1]; const kids = new Array(); const parents = new Array(); for (let i = 0; i < n; i++) { parents.push(root); kids.push(new Set()); } for (let i = root; i - 1 >= 0; i--) { parents[i - 1] = i; kids[i].add(i - 1); } for (let i = root; i + 1 < n; i++) { parents[i + 1] = i; kids[i].add(i + 1); } for (let k = 0; k < 20; k++) { const leafId = Math.floor(Math.random() * leaves.length); const leaf = leaves[leafId]; // Delete edge kids[parents[leaf]].delete(leaf); let newParent = Math.floor(Math.random() * n); if (newParent >= leaf) { newParent += 1; } if (kids[newParent].size == 0) { leaves; // remove newp } kids[newParent].add(leaf); parents[leaf] = newParent; } const graph = new Graph(n); for (let i = 0; i < n - 1; i++) { graph.addArc(i, i + 1); } return graph; } static circulantTournament(n, gaps) { const graph = new Graph(2 * n + 1); for (let i = 0; i < (2 * n + 1); i++) { for (const k of gaps) { const j = ((2 * n + 1) + i + k) % (2 * n + 1); graph.addArc(i, j); } } return graph; } /** * PaleyGraph is unoriented if p = 1 mod 4. * It is oriented if p = -1 mod 4. * @param p should be a prime number = +-1 mod 4 * @returns Error if p is not such a number * @example undirected: 5 13 17, directed: 3 7 11 */ static Paley(p) { if (Number.isInteger(p) == false) throw Error(`p (given ${p}) should be an integer`); if ((p - 1) % 4 != 0 && (p + 1) % 4 != 0) throw Error(`param p (given ${p}) should be = +-1 mod 4 (here p = ${p % 4} mod 4)`); const orientation = (p - 1) % 4 == 0 ? link_1.ORIENTATION.UNDIRECTED : link_1.ORIENTATION.DIRECTED; const graph = new Graph(p); if (orientation == link_1.ORIENTATION.UNDIRECTED) { for (let i = 0; i < p; i++) { for (let j = i + 1; j < p; j++) { if ((0, utils_1.isModSquare)(j - i, p)) { graph.addArc(i, j); } } } } else { for (let i = 0; i < p; i++) { for (let j = 0; j < p; j++) { if (i != j && (0, utils_1.isModSquare)(j - i, p)) { graph.addArc(i, j); } } } } return graph; } /** * The line graph is the graph associated to an undirected graph where the vertices are the edges of the initial graph. * Two edges are adjacent in the line graph if they share a common endpoint. * @returns */ static lineGraph(graph) { const g = new Graph(graph.links.size); for (const link1 of graph.links.values()) { for (const link2 of graph.links.values()) { if (link1.index <= link2.index) continue; if (link1.startVertex.index == link2.startVertex.index || link1.startVertex.index == link2.endVertex.index || link1.endVertex.index == link2.startVertex.index || link1.endVertex.index == link2.endVertex.index) { g.addEdge(link1.index, link2.index); } } } return g; } /** * Return the geometric line graph is the graph whose vertices are the links of the initial graph. * Two links are considered adjacent if the geometric paths intersect (they can intersect at their endpoints). * Therefore the geometric line graph is a super graph of the line graph. * @example for K4 * o---o * |\ /| This K4 embedding * | X | has one more edge in the geometric line graph * |/ \| * o---o * * @example * o * /|\ * / | \ This K4 embedding * / o \ has the same geometric line graph and line graph * /__/ \__\ * o---------o * * */ static geometricLineGraph(graph) { const g = new Graph(graph.links.size); for (const link1 of graph.links.values()) { for (const link2 of graph.links.values()) { if (link1.index <= link2.index) continue; if (link1.startVertex.index == link2.startVertex.index || link1.startVertex.index == link2.endVertex.index || link1.endVertex.index == link2.startVertex.index || link1.endVertex.index == link2.endVertex.index) { g.addEdge(link1.index, link2.index); } else if (link1.intersectsLink(link2)) { g.addArc(link1.index, link2.index); } } } return g; } /** * Returns an Undirected Graph from a list of edges represented by couples of indices. * Weights are set to "". */ // static from_list(l: Array<[number,number]>): Graph<Vertex,Link>{ // const l2 = new Array(); // for (const [x,y] of l){ // l2.push([x,y,""]); // } // const g = Graph.from_list_default(l2, Vertex.default, Link.default_edge ); // return g; // } // create a Weighted Undirected Graph from a list of weighted edges represented by couples of number with the weight in third // static from_weighted_list(l: Array<[number,number,string]>): Graph<Vertex,Link>{ // const g = Graph.from_list_default(l, Vertex.default, Link.default_edge ); // return g; // } // static directed_from_list(l: Array<[number,number]>): Graph<Vertex,Link>{ // const g = Graph.directed_from_list_default(l, Vertex.default, Link.default_arc ); // return g; // } // static directed_from_list_default<V extends Vertex,L extends Link<L>>(l: Array<[number,number]>, vertex_default: (index: number)=> V, arc_default: (x: number, y: number, weight: string) => L ): Graph{ // const g = new Graph(); // const indices = new Set<number>(); // for ( const [x,y] of l.values()){ // if (indices.has(x) == false){ // indices.add(x); // g.setVertex(x,vertex_default(x)); // } // if (indices.has(y) == false){ // indices.add(y); // g.setVertex(y,vertex_default(y)); // } // const link = arc_default(x,y,""); // g.addArc(link); // } // return g; // } /** * Return the list of the extremeties of the arcs. */ arcsList() { const l = new Array(); for (const link of this.links.values()) { if (link.orientation == link_1.ORIENTATION.DIRECTED) { l.push([link.startVertex.index, link.endVertex.index]); } } return l; } // update_vertex_pos(vertex_index: number, new_pos: Coord) { // const vertex = this.vertices.get(vertex_index); // if (typeof vertex !== "undefined"){ // vertex.pos = new_pos; // } // } /** * WIP * @returns */ hasLightExtension() { const [b, done] = (0, isTournamentLight_1.hasLightTournamentExtension2)(this); console.log(done); return b; } /** * A tournament is light if and only there does not exist different vertices u,v,a,b,c such that * - u -> v * - a -> b -> c -> a is an oriented triangle * - v -> a,b,c * - a,b,c -> u * @returns undefined or a conflict [u,v,a,b,c] */ lightnessConflict() { // const m = this.getDirectedMatrix(); const heavyArc = (0, isTournamentLight_1.searchHeavyArcDigraph)(this); if (heavyArc.length == 0) { return undefined; } else { return heavyArc; } } /** * A tournament is light if and only there does not exist different vertices u,v,a,b,c such that * - u -> v * - a -> b -> c -> a is an oriented triangle * - v -> a,b,c * - a,b,c -> u * @remark If the graph is not light and you want to get 5 such vertices use the method `lightnessConflict()` */ isTournamentLight() { return (0, isTournamentLight_1.isTournamentLight)(this); } /** * * @param link * @returns false if * - link is a loop * - there is already a link with the same signature (same orientation and start and end) */ chekLink(link) { const i = link.startVertex.index; const j = link.endVertex.index; const orientation = link.orientation; // do not add link if it is a loop (NO LOOP) if (i == j) { return false; } // do not add link if it was already existing (NO MULTIEDGE) for (const link of this.links.values()) { if (link.orientation == orientation) { if (orientation == link_1.ORIENTATION.UNDIRECTED) { if ((link.startVertex.index == i && link.endVertex.index == j) || (link.startVertex.index == j && link.endVertex.index == i)) { return false; } } else if (orientation == link_1.ORIENTATION.DIRECTED) { if (link.startVertex.index == i && link.endVertex.index == j) { return false; } } } } return true; } getNeighborsListExcludingLinks(i, excluded) { const neighbors = new Array(); for (const [link_index, link] of this.links.entries()) { if (excluded.has(link_index) == false && link.orientation == link_1.ORIENTATION.UNDIRECTED) { if (link.startVertex.index == i) { neighbors.push(link.endVertex.index); } else if (link.endVertex.index == i) { neighbors.push(link.startVertex.index); } } } return neighbors; } /** * * @param vId vertex * @param d distance >= 0 (when d = 1, then it returns the closed in-neighborhood) * @returns the closed in-neighborhood of `vId` at dist at most `d`. That is the vertices at distance at most d to `vId`. * @example * Graph.orientedPath(3).getClosedDistInNeighborhood(2, 2); // = [0,1,2] * Graph.orientedPath(3).getClosedDistInNeighborhood(2, 1); // = [1,2] */ getClosedDistInNeighborhood(v, d) { if (d <= 0) { return [v]; } else { const neighborsPrec = this.getClosedDistInNeighborhood(v, d - 1); const neighborsD = new Array(); for (const neighbor of neighborsPrec) { if (neighborsD.indexOf(neighbor) == -1) { neighborsD.push(neighbor); } for (const nId of neighbor.inNeighbors) { if (neighborsD.indexOf(nId) == -1) { neighborsD.push(nId); } } } return neighborsD; } } /** * * @param vId vertex index * @param d distance >= 0 (when d = 1, then it returns the closed in-neighborhood) * @returns the closed in-neighborhood of `vId` at dist at most `d`. That is the vertices at distance at most d from `vId`. * @example * Graph.orientedPath(3).getClosedDistOutNeighborhood(0, 2); // = [0,1,2] * Graph.orientedPath(3).getClosedDistOutNeighborhood(0, 1); // = [0,1] */ getClosedDistOutNeighborhood(v, d) { if (d <= 0) { return [v]; } else { const neighborsPrec = this.getClosedDistOutNeighborhood(v, d - 1); const neighborsD = new Array(); for (const neighbor of neighborsPrec) { if (neighborsD.indexOf(neighbor) == -1) { neighborsD.push(neighbor); } for (const nId of neighbor.outNeighbors) { if (neighborsD.indexOf(nId) == -1) { neighborsD.push(nId); } } } return neighborsD; } } getDegreesData() { if (this.vertices.size == 0) { return { min_value: 0, min_vertices: null, max_value: 0, max_vertices: null, avg: 0 }; } const v = this.vertices.values().next().value; if (typeof v == "undefined") { return { min_value: 0, min_vertices: null, max_value: 0, max_vertices: null, avg: 0 }; } let min_indices = new Set([v.index]); let min_degree = v.degree(); let max_indices = new Set([v.index]); let maxDegree = v.degree(); let average = 0.0; for (const v of this.vertices.values()) { if (min_degree > v.degree()) { min_degree = v.degree(); min_indices = new Set([v.index]); } if (min_degree === v.degree()) { min_indices.add(v.index); } if (maxDegree < v.degree()) { maxDegree = v.degree(); max_indices = new Set([v.index]); } if (maxDegree === v.degree()) { max_indices.add(v.index); } average += v.degree(); } average = average / this.vertices.size; return { min_value: min_degree, min_vertices: min_indices, max_value: maxDegree, max_vertices: max_indices, avg: average }; } /** * Return maximum (undirected) degree of the graph. * Out-neighbors and In-neighbors are not taken in account. * @returns -1 if there is no vertex * @TODO should return undefined if no vertex */ maxDegree() { let record = -1; for (const v of this.vertices.values()) { if (v.degree() > record) { record = v.degree(); } } return record; } /** * Return the minimum (undirected) degree of the graph. * Out-neighbors and In-neighbors are not considered. * @returns Infinity if there is no vertex */ minDegree() { let record = Infinity; for (const v of this.vertices.values()) { if (v.degree() < record) { record = v.degree(); } } return record; } /** * Return minimum in-degree of the graph * @returns `""` if there is no vertex * @TODO replace string return by undefined */ minIndegree() { let record = ""; for (const v of this.vertices.values()) { let indegree = v.indegree(); if (typeof record == "string") { record = indegree; } else if (indegree < record) { record = indegree; } } return record; } /** * Return maximum in-degree of the graph * @returns undefined if there is no vertex */ maxIndegree() { let record = undefined; for (const v of this.vertices.values()) { let indegree = v.indegree(); if (typeof record == "undefined") { record = indegree; } else if (indegree > record) { record = indegree; } } return record; } /** * Return maximum out-degree of the graph * @returns undefined if there is no vertex */ maxOutdegree() { let record = undefined; for (const v of this.vertices.values()) { let d = v.outdegree(); if (typeof record == "undefined") { record = d; } else if (d > record) { record = d; } } return record; } // return minimum outdegree of the graph // return "" if there is no vertex minOutdegree() { let record = ""; for (const v of this.vertices.values()) { let indegree = v.outdegree(); if (typeof record == "string") { record = indegree; } else if (indegree < record) { record = indegree; } } return record; } DFSrecursive(v, visited) { visited.set(v.index, true); for (const u of v.neighbors.values()) { if (visited.has(u.index) && !visited.get(u.index)) { this.DFSrecursive(u, visited); } } } DFSiterative(v) { const visited = new Map(); for (const index of this.vertices.keys()) { visited.set(index, false); } console.log(visited); const S = Array(); S.push(v); while (S.length !== 0) { const u = S.pop(); if (typeof u != "undefined" && !visited.get(u)) { visited.set(u, true); for (const neighbor of u.neighbors.values()) { S.push(neighbor); } } } return visited; } hasCycle() { let ok_list = new Set(); let g = this; function _hasCycle(d, origin, s) { for (const v of d.neighbors.values()) { if ((typeof origin != "undefined" && v.index == origin.index) || ok_list.has(v)) { continue; } if (s.indexOf(v.index) > -1) { return true; } s.push(v.index); let b = _hasCycle(v, d, s); if (b) { return true; } ok_list.add(v); s.pop(); } return false; } for (const v of this.vertices.values()) { if (ok_list.has(v)) { continue; } if (_hasCycle(v, undefined, [v.index])) { return true; } } return false; } /** * @returns [b, cycle] where b is a boolean which is true iff there exists a cycle. * If b is true, a cycle is returned. * @remark Iterative version */ hasCycle2() { const visited = new Set(); for (const v of this.vertices.values()) { // console.log("start", v); if (visited.has(v) == false) { const stack = new Array(); const previous = new Map(); stack.push([v, -1]); let r = stack.pop(); while (typeof r != "undefined") { const [u, last] = r; // console.log("stack", uIndex); if (visited.has(u)) { console.log("bug"); return [true, []]; } visited.add(u); for (const neighbor of u.neighbors.values()) { if (neighbor.index != last) { if (visited.has(neighbor) == false) { previous.set(neighbor.index, u); stack.push([neighbor, u.index]); } else { const cycle = new Array(); cycle.push(neighbor); cycle.push(u); let j = previous.get(u.index); while (typeof j != "undefined" && j != neighbor) { cycle.push(j); j = previous.get(j.index); } return [true, cycle]; } } } r = stack.pop(); // console.log([...previous]) // console.log("--") } } } return [false, []]; } /** * * @returns girth of the graph. It is the minimum length of a cycle. * If there is no cycle then `Infinity` is returned. * If there is a cycle, a list of its consecutive vertices is returned. * * @example * Graph.generateClique(4).girth() == 3 * Graph.generatePaley(13).girth() == 3 * Graph.petersen().girth() == 5 * Graph.star(3).girth() == 0 */ girth() { const cycle = this.shortestCycle(); if (cycle.length == 0) { return Infinity; } else { return cycle.length; } } /** * @returns a shortest cycle. It is a cycle of minimum length. * Returns an empty array if there is no cycle. * * @example * Graph.generateClique(4).shortestCycle().length == 3 * Graph.generatePaley(13).shortestCycle().length == 3 * Graph.petersen.shortestCycle().length == 5 * Graph.star(3).shortestCycle().length == 0 */ shortestCycle() { let girth = Infinity; const shortestCycle = new Array(); for (const v of this.vertices.values()) { const visited = new Set(); const distances = new Map(); const predecessors = new Map(); // console.log("starting vertex", v); if (!visited.has(v)) { const queue = new Array(); queue.push([v, 0, v]); // Queue for BFS, each element is [vertex, distance, predecessor] // console.log("init push ", [v,0, Infinity]); while (queue.length > 0) { const elt = queue.shift(); if (typeof elt != "undefined") { const [current, distance, prede] = elt; if (visited.has(current)) { // Cycle detected because current was already visited // It means that it has a shortest path to v of length distances[current] // We have also reached current from prede // console.log("cycle ", current, "dist", distance, "from ", prede, "and", predecessors.get(current)) const d = distances.get(current.index); if (typeof d != "undefined") { const cycleLength = distance + d; // console.log("cycle length", cycleLength); if (cycleLength < girth) { girth = cycleLength; // Reconstruct the shortest cycle by computing the path from current to v // and from prede to v let cycleStart = current; shortestCycle.splice(0, shortestCycle.length); shortestCycle.push(cycleStart); while (cycleStart !== v) { const pred = predecessors.get(cycleStart.index); if (typeof pred != "undefined") { cycleStart = pred; shortestCycle.push(cycleStart); } else { break; } } cycleStart = prede; while (cycleStart !== v) { shortestCycle.unshift(cycleStart); const pred = predecessors.get(cycleStart.index); if (typeof pred != "undefined") { cycleStart = pred; } else { break; } } // console.log("final cycle", shortestCycle) } } } else { // console.log("visit ", current, "dist ", distance, "pred ", prede); visited.add(current.index); distances.set(current.index, distance); predecessors.set(current.index, prede); // Set the predecessor to the last vertex in the queue for (const neighbor of current.neighbors.values()) { if (!visited.has(neighbor.index)) { // console.log("push" , neighbor, distance+1) queue.push([neighbor, distance + 1, current]); } } } } } } } return shortestCycle; } /** * @returns [b, cycle] where b is a boolean which is true iff there exists a directed cycle. * If b is true, a directed cycle is returned. * @remark Iterative version */ getDirectedCycle() { const outNeighbors = new Map(); for (const v of this.vertices.values()) { const vOutNeighbors = new Set(); for (const neigh of v.outNeighbors.values()) { vOutNeighbors.add(neigh.index); } outNeighbors.set(v.index, vOutNeighbors); } return (0, cycle_1.getDirectedCycle)(outNeighbors.keys(), outNeighbors); // const state = new Map<number, number>(); // // if a vertexIndex is a key of state, then the value is either 1 for DISCOVERED // // either 2 for TREATED, which means that no cycle start from this vertex // // if a vertexIndex is not a key, then is is considered as UNDISCOVERED // for (const v of this.vertices.keys()) { // if ( state.has(v) == false){ // const stack = new Array<number>(); // const previous = new Map<number,number>(); // stack.push(v); // while (stack.length > 0){ // const u = stack[stack.length-1]; // if (state.has(u) == false){ // state.set(u, 1); // 1 is DISCOVERED // const neighbors = this.getOutNeighborsList(u); // for (const uNeighbor of neighbors) { // if ( state.has(uNeighbor) == false){ // previous.set(uNeighbor, u); // stack.push(uNeighbor); // } else if (state.get(uNeighbor) == 1) { // const cycle = new Array<number>(); // cycle.push(uNeighbor); // cycle.push(u); // let j = previous.get(u); // while ( typeof j != "undefined" && j != uNeighbor){ // cycle.push(j); // j = previous.get(j); // } // return cycle; // } // } // } // else {