gramoloss
Version:
Graph theory package for edition and computation
1,608 lines (1,329 loc) • 130 kB
text/typescript
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