UNPKG

gramoloss

Version:

Graph theory package for edition and computation

1,608 lines (1,329 loc) 130 kB
import { Link, ORIENTATION } from './link'; import { Vertex, VertexIndex } from './vertex'; import { Coord, Vect } from './coord'; import { bezierCurvePoint, det, isQuadraticBezierCurvesIntersection, isSegmentsIntersection, isModSquare, isPrime, segmentsInteriorIntersection } from './utils'; import { Option } from "./option"; import { minDFVS } from './algorithms/dfvs'; import { getDirectedCycle } from './algorithms/cycle'; import { hasLightTournamentExtension2, isTournamentLight, searchHeavyArc, searchHeavyArcDigraph } from './algorithms/isTournamentLight'; import { acyclicColoring, dichromatic } from './algorithms/dichromatic'; /** * For the Dominating Set Algo * @todo should be removed by implementing a method for IDS */ export enum DominationVariant { Independent, OrientedIndependent } export class Graph { vertices: Map<VertexIndex, Vertex>; links: Map<number, Link>; matrix: Array<Array<number>>; private n: number = 0; private linksCounter: number = 0; constructor(n?: number) { 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(): number { return this.n; } nbEdges(): number { let c = 0; for (const link of this.links.values()){ if (link.orientation == ORIENTATION.UNDIRECTED){ c += 1; } } return c; } nbArcs(): number { let c = 0; for (const link of this.links.values()){ if (link.orientation == ORIENTATION.DIRECTED){ c += 1; } } return c; } getAvailableVertexIndex(): VertexIndex { let c = this.vertices.size; while (this.vertices.has(c)){ c += 1; } return c; } /** * Add a vertex to the graph. */ addVertex(index: VertexIndex, x?: number, y?: number): Vertex { const vx = x ?? 0; const vy = y ?? 0; const newVertex = new Vertex( this, index, this.n, vx, vy); this.vertices.set(index, newVertex); this.n += 1; this.matrix.push(new Array<number>(this.n).fill(0)); for (let i = 0; i < this.n-1 ; i ++){ this.matrix[i].push(0); } return newVertex; } deleteVertex(v: Vertex) { // 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 == 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 == 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: Vertex | VertexIndex, v: Vertex | VertexIndex): Link | Error{ 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(index, this, source, target,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: Vertex | VertexIndex, b: Vertex | VertexIndex){ const u = (a instanceof Vertex) ? a : this.vertices.get(a); const v = (b instanceof 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: Vertex, v: Vertex){ 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 == 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: Vertex | VertexIndex, v: Vertex | VertexIndex): Link | Error{ 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(index, this, source, target, 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: Vertex | VertexIndex, b: Vertex | VertexIndex){ const u = (a instanceof Vertex) ? a : this.vertices.get(a); const v = (b instanceof 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: VertexIndex | Vertex): number{ if (v instanceof 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: VertexIndex | Vertex): number{ if (v instanceof 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: VertexIndex | Vertex): number{ if (v instanceof 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: number): number { 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: Array<[number,number, string?]>, verticesPositions?: Array<[number, number]> ){ 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){ edge.weight = w; } } return g; } /** * Returns an undirected Graph given by its edges. * @param arcs */ static fromArcs( arcs: Array<[number,number, string?]>, verticesPositions?: Array<[number, number]> ){ 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){ arc.weight = w; } } return g; } /** * @returns Petersen graph (10 vertices, 15 edges) * @see https://en.wikipedia.org/wiki/Petersen_graph */ static petersen(): Graph{ 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: number, p: number): Graph { 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: Array<number>): Graph { 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: number, m: number): Graph { 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: number, p: number): Graph { 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: number): Graph { return Graph.UGtournament(n, 0); } /** * for every i < j, i -> j iff i+j is prime * @param n */ static testTournament(n: number): Graph { const graph = new Graph(n); for ( let i = 0 ; i < n ; i ++){ for ( let j = 0 ; j < i ; j ++ ){ if (isPrime(i+j)){ graph.addArc(j, i); } else { graph.addArc(i, j); } } } return graph; } static generateAztecDiamond(n: number): Graph { const graph = new Graph(); function check(i: number,j: number,n: number): boolean { 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: number, m: number): Graph { 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: number){ const g = Graph.path(n); g.addEdge(n-1, 0); return g; } static clique(n: number): Graph{ 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: number): Graph{ 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: number): Graph{ 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: number): Graph{ 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: number, k: number): Graph { 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: number, d: number): Graph { const graph = new Graph(); const vertices = new Array<Vertex>(); 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: number): Graph { 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: number): Graph { 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: number): Graph { return new Graph(n); } /** * With Markov Chain strategy * @param n number of vertices * @returns a tree */ static randomTree(n: number): Graph { const root = Math.floor(n/2); const leaves = [0,n-1]; const kids = new Array<Set<number>>(); 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: number, gaps: Array<number>): Graph { 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: number): Graph { 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 ? ORIENTATION.UNDIRECTED : ORIENTATION.DIRECTED; const graph = new Graph(p); if (orientation == ORIENTATION.UNDIRECTED){ for ( let i = 0 ; i < p ; i ++){ for (let j = i+1 ; j < p ; j ++){ if ( 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 && 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: Graph): 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:Graph): 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(): Array<[VertexIndex, VertexIndex]>{ const l = new Array<[VertexIndex, VertexIndex]>(); for (const link of this.links.values()){ if (link.orientation == 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(): boolean { const [b,done] = 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(): Option<Array<Vertex>> { // const m = this.getDirectedMatrix(); const heavyArc = 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(): boolean { return 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: Link): boolean { 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 == ORIENTATION.UNDIRECTED) { if ((link.startVertex.index == i && link.endVertex.index == j) || (link.startVertex.index == j && link.endVertex.index == i)) { return false; } } else if (orientation == ORIENTATION.DIRECTED) { if (link.startVertex.index == i && link.endVertex.index == j) { return false; } } } } return true; } getNeighborsListExcludingLinks(i: VertexIndex, excluded: Set<VertexIndex>): Array<VertexIndex> { const neighbors = new Array<VertexIndex>(); for (const [link_index, link] of this.links.entries()) { if (excluded.has(link_index) == false && link.orientation == 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: Vertex, d: number): Array<Vertex> { 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: Vertex, d: number): Array<Vertex> { 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(): number{ 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(): number{ 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(): number | string{ let record: number | string = ""; 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(): number | undefined{ let record: number | undefined = 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(): number | undefined{ let record: number | undefined = 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(): number | string{ let record: number | string = ""; 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: Vertex, visited: Map<VertexIndex, boolean>) { 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: Vertex) { const visited = new Map(); for (const index of this.vertices.keys()) { visited.set(index, false); } console.log(visited); const S = Array<Vertex>(); 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(): boolean { let ok_list = new Set(); let g = this; function _hasCycle(d: Vertex, origin: Vertex | undefined, s: Array<VertexIndex>): boolean { 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(): [boolean, Array<Vertex>] { const visited = new Set(); for (const v of this.vertices.values()) { // console.log("start", v); if ( visited.has(v) == false){ const stack = new Array<[Vertex, VertexIndex]>(); const previous = new Map<VertexIndex, Vertex>(); 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<Vertex>(); 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(): number{ 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(): Array<Vertex> { let girth = Infinity; const shortestCycle = new Array<Vertex>(); for (const v of this.vertices.values()) { const visited = new Set(); const distances = new Map<VertexIndex, number>(); const predecessors = new Map<VertexIndex, Vertex>(); // console.log("starting vertex", v); if (!visited.has(v)) { const queue = new Array<[Vertex, number, Vertex]>(); 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(): undefined | Array<VertexIndex> { const outNeighbors = new Map<VertexIndex, Set<VertexIndex>>(); for (const v of this.vertices.values()){ const vOutNeighbors = new Set<VertexIndex>(); for (const neigh of v.outNeighbors.values()){ vOutNeighbors.add(neigh.index); } outNeighbors.set(v.index, vOutNeighbors); } return 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){ // co