gramoloss
Version:
Graph theory package for edition and computation
1,383 lines (1,382 loc) • 127 kB
JavaScript
"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 {