int-adjacency-graph.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { arrayPickIndices, cantorPairing } from '../../mol-data/util';
  8. import { LinkedIndex, SortedArray } from '../../mol-data/int';
  9. import { AssignableArrayLike } from '../../mol-util/type-helpers';
  10. /**
  11. * Represent a graph using vertex adjacency list.
  12. *
  13. * Edges of the i-th vertex are stored in the arrays a and b
  14. * for indices in the range [offset[i], offset[i+1]).
  15. *
  16. * Edge properties are indexed same as in the arrays a and b.
  17. */
  18. export interface IntAdjacencyGraph<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase> {
  19. readonly offset: ArrayLike<number>,
  20. readonly a: ArrayLike<VertexIndex>,
  21. readonly b: ArrayLike<VertexIndex>,
  22. readonly vertexCount: number,
  23. readonly edgeCount: number,
  24. readonly edgeProps: Readonly<EdgeProps>
  25. /**
  26. * Get the edge index between i-th and j-th vertex.
  27. * -1 if the edge does not exist.
  28. *
  29. * Because the a and b arrays contains each edge twice,
  30. * this always returns the smaller of the indices.
  31. *
  32. * `getEdgeIndex(i, j) === getEdgeIndex(j, i)`
  33. */
  34. getEdgeIndex(i: VertexIndex, j: VertexIndex): number,
  35. /**
  36. * Get the edge index between i-th and j-th vertex.
  37. * -1 if the edge does not exist.
  38. *
  39. * `getEdgeIndex(i, j) !== getEdgeIndex(j, i)`
  40. */
  41. getDirectedEdgeIndex(i: VertexIndex, j: VertexIndex): number,
  42. getVertexEdgeCount(i: VertexIndex): number
  43. }
  44. export namespace IntAdjacencyGraph {
  45. export type EdgePropsBase = { [name: string]: ArrayLike<any> }
  46. class IntGraphImpl<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase> implements IntAdjacencyGraph<VertexIndex, EdgeProps> {
  47. readonly vertexCount: number;
  48. readonly edgeProps: EdgeProps;
  49. getEdgeIndex(i: VertexIndex, j: VertexIndex): number {
  50. let a, b;
  51. if (i < j) {
  52. a = i; b = j;
  53. } else {
  54. a = j; b = i;
  55. }
  56. for (let t = this.offset[a], _t = this.offset[a + 1]; t < _t; t++) {
  57. if (this.b[t] === b) return t;
  58. }
  59. return -1;
  60. }
  61. getDirectedEdgeIndex(i: VertexIndex, j: VertexIndex): number {
  62. for (let t = this.offset[i], _t = this.offset[i + 1]; t < _t; t++) {
  63. if (this.b[t] === j) return t;
  64. }
  65. return -1;
  66. }
  67. getVertexEdgeCount(i: VertexIndex): number {
  68. return this.offset[i + 1] - this.offset[i];
  69. }
  70. constructor(public offset: ArrayLike<number>, public a: ArrayLike<VertexIndex>, public b: ArrayLike<VertexIndex>, public edgeCount: number, edgeProps?: EdgeProps) {
  71. this.vertexCount = offset.length - 1;
  72. this.edgeProps = (edgeProps || {}) as EdgeProps;
  73. }
  74. }
  75. export function create<VertexIndex extends number, EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(offset: ArrayLike<number>, a: ArrayLike<VertexIndex>, b: ArrayLike<VertexIndex>, edgeCount: number, edgeProps?: EdgeProps): IntAdjacencyGraph<VertexIndex, EdgeProps> {
  76. return new IntGraphImpl(offset, a, b, edgeCount, edgeProps) as IntAdjacencyGraph<VertexIndex, EdgeProps>;
  77. }
  78. export class EdgeBuilder<VertexIndex extends number> {
  79. private bucketFill: Int32Array;
  80. private current = 0;
  81. private curA: number = 0;
  82. private curB: number = 0;
  83. offsets: Int32Array;
  84. edgeCount: number;
  85. /** the size of the A and B arrays */
  86. slotCount: number;
  87. a: AssignableArrayLike<VertexIndex>;
  88. b: AssignableArrayLike<VertexIndex>;
  89. createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(edgeProps: EdgeProps) {
  90. return create<VertexIndex, EdgeProps>(this.offsets, this.a, this.b, this.edgeCount, edgeProps);
  91. }
  92. /**
  93. * @example
  94. * const property = new Int32Array(builder.slotCount);
  95. * for (let i = 0; i < builder.edgeCount; i++) {
  96. * builder.addNextEdge();
  97. * builder.assignProperty(property, srcProp[i]);
  98. * }
  99. * return builder.createGraph({ property });
  100. */
  101. addNextEdge() {
  102. const a = this.xs[this.current], b = this.ys[this.current];
  103. const oa = this.offsets[a] + this.bucketFill[a];
  104. const ob = this.offsets[b] + this.bucketFill[b];
  105. this.a[oa] = a;
  106. this.b[oa] = b;
  107. this.bucketFill[a]++;
  108. this.a[ob] = b;
  109. this.b[ob] = a;
  110. this.bucketFill[b]++;
  111. this.current++;
  112. this.curA = oa;
  113. this.curB = ob;
  114. }
  115. /** Builds property-less graph */
  116. addAllEdges() {
  117. for (let i = 0; i < this.edgeCount; i++) {
  118. this.addNextEdge();
  119. }
  120. }
  121. assignProperty<T>(prop: { [i: number]: T }, value: T) {
  122. prop[this.curA] = value;
  123. prop[this.curB] = value;
  124. }
  125. constructor(public vertexCount: number, public xs: ArrayLike<VertexIndex>, public ys: ArrayLike<VertexIndex>) {
  126. this.edgeCount = xs.length;
  127. this.offsets = new Int32Array(this.vertexCount + 1);
  128. this.bucketFill = new Int32Array(this.vertexCount);
  129. const bucketSizes = new Int32Array(this.vertexCount);
  130. for (let i = 0, _i = this.xs.length; i < _i; i++) bucketSizes[this.xs[i]]++;
  131. for (let i = 0, _i = this.ys.length; i < _i; i++) bucketSizes[this.ys[i]]++;
  132. let offset = 0;
  133. for (let i = 0; i < this.vertexCount; i++) {
  134. this.offsets[i] = offset;
  135. offset += bucketSizes[i];
  136. }
  137. this.offsets[this.vertexCount] = offset;
  138. this.slotCount = offset;
  139. this.a = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
  140. this.b = new Int32Array(offset) as unknown as AssignableArrayLike<VertexIndex>;
  141. }
  142. }
  143. export class DirectedEdgeBuilder<VertexIndex extends number> {
  144. private bucketFill: Int32Array;
  145. private current = 0;
  146. private curA: number = 0;
  147. offsets: Int32Array;
  148. edgeCount: number;
  149. /** the size of the A and B arrays */
  150. slotCount: number;
  151. a: Int32Array;
  152. b: Int32Array;
  153. createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase>(edgeProps: EdgeProps) {
  154. return create(this.offsets, this.a, this.b, this.edgeCount, edgeProps);
  155. }
  156. /**
  157. * @example
  158. * const property = new Int32Array(builder.slotCount);
  159. * for (let i = 0; i < builder.edgeCount; i++) {
  160. * builder.addNextEdge();
  161. * builder.assignProperty(property, srcProp[i]);
  162. * }
  163. * return builder.createGraph({ property });
  164. */
  165. addNextEdge() {
  166. const a = this.xs[this.current], b = this.ys[this.current];
  167. const oa = this.offsets[a] + this.bucketFill[a];
  168. this.a[oa] = a;
  169. this.b[oa] = b;
  170. this.bucketFill[a]++;
  171. this.current++;
  172. this.curA = oa;
  173. }
  174. /** Builds property-less graph */
  175. addAllEdges() {
  176. for (let i = 0; i < this.edgeCount; i++) {
  177. this.addNextEdge();
  178. }
  179. }
  180. assignProperty<T>(prop: { [i: number]: T }, value: T) {
  181. prop[this.curA] = value;
  182. }
  183. constructor(public vertexCount: number, public xs: ArrayLike<VertexIndex>, public ys: ArrayLike<VertexIndex>) {
  184. this.edgeCount = xs.length;
  185. this.offsets = new Int32Array(this.vertexCount + 1);
  186. this.bucketFill = new Int32Array(this.vertexCount);
  187. const bucketSizes = new Int32Array(this.vertexCount);
  188. for (let i = 0, _i = this.xs.length; i < _i; i++) bucketSizes[this.xs[i]]++;
  189. let offset = 0;
  190. for (let i = 0; i < this.vertexCount; i++) {
  191. this.offsets[i] = offset;
  192. offset += bucketSizes[i];
  193. }
  194. this.offsets[this.vertexCount] = offset;
  195. this.slotCount = offset;
  196. this.a = new Int32Array(offset);
  197. this.b = new Int32Array(offset);
  198. }
  199. }
  200. export class UniqueEdgeBuilder<VertexIndex extends number> {
  201. private xs: VertexIndex[] = [];
  202. private ys: VertexIndex[] = [];
  203. private included = new Set<number>();
  204. addEdge(i: VertexIndex, j: VertexIndex) {
  205. let u = i, v = j;
  206. if (i > j) { u = j; v = i; }
  207. const id = cantorPairing(u, v);
  208. if (this.included.has(id)) return false;
  209. this.included.add(id);
  210. this.xs[this.xs.length] = u;
  211. this.ys[this.ys.length] = v;
  212. return true;
  213. }
  214. getGraph(): IntAdjacencyGraph<VertexIndex, {}> {
  215. return fromVertexPairs(this.vertexCount, this.xs, this.ys);
  216. }
  217. // if we cant to add custom props as well
  218. getEdgeBuiler() {
  219. return new EdgeBuilder(this.vertexCount, this.xs, this.ys);
  220. }
  221. constructor(public vertexCount: number) {
  222. }
  223. }
  224. export function fromVertexPairs<V extends number>(vertexCount: number, xs: V[], ys: V[]) {
  225. const graphBuilder = new IntAdjacencyGraph.EdgeBuilder(vertexCount, xs, ys);
  226. graphBuilder.addAllEdges();
  227. return graphBuilder.createGraph({});
  228. }
  229. export function induceByVertices<V extends number, P extends IntAdjacencyGraph.EdgePropsBase>(graph: IntAdjacencyGraph<V, P>, vertexIndices: ArrayLike<number>): IntAdjacencyGraph<V, P> {
  230. const { b, offset, vertexCount, edgeProps } = graph;
  231. const vertexMap = new Int32Array(vertexCount);
  232. for (let i = 0, _i = vertexIndices.length; i < _i; i++) vertexMap[vertexIndices[i]] = i + 1;
  233. let newEdgeCount = 0;
  234. for (let i = 0; i < vertexCount; i++) {
  235. if (vertexMap[i] === 0) continue;
  236. for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) {
  237. if (b[j] > i && vertexMap[b[j]] !== 0) newEdgeCount++;
  238. }
  239. }
  240. const newOffsets = new Int32Array(vertexIndices.length + 1);
  241. const edgeIndices = new Int32Array(2 * newEdgeCount);
  242. const newA = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
  243. const newB = new Int32Array(2 * newEdgeCount) as unknown as AssignableArrayLike<V>;
  244. let eo = 0, vo = 0;
  245. for (let i = 0; i < vertexCount; i++) {
  246. if (vertexMap[i] === 0) continue;
  247. const aa = vertexMap[i] - 1;
  248. for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) {
  249. const bb = vertexMap[b[j]];
  250. if (bb === 0) continue;
  251. newA[eo] = aa as V;
  252. newB[eo] = bb - 1 as V;
  253. edgeIndices[eo] = j;
  254. eo++;
  255. }
  256. newOffsets[++vo] = eo;
  257. }
  258. const newEdgeProps = {} as P;
  259. for (const key of Object.keys(edgeProps) as (keyof P)[]) {
  260. newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices) as P[keyof P];
  261. }
  262. return create(newOffsets, newA, newB, newEdgeCount, newEdgeProps);
  263. }
  264. export function connectedComponents(graph: IntAdjacencyGraph<any, any>): { componentCount: number, componentIndex: Int32Array } {
  265. const vCount = graph.vertexCount;
  266. if (vCount === 0) return { componentCount: 0, componentIndex: new Int32Array(0) };
  267. if (graph.edgeCount === 0) {
  268. const componentIndex = new Int32Array(vCount);
  269. for (let i = 0, _i = vCount; i < _i; i++) {
  270. componentIndex[i] = i;
  271. }
  272. return { componentCount: vCount, componentIndex };
  273. }
  274. const componentIndex = new Int32Array(vCount);
  275. for (let i = 0, _i = vCount; i < _i; i++) componentIndex[i] = -1;
  276. let currentComponent = 0;
  277. componentIndex[0] = currentComponent;
  278. const { offset, b: neighbor } = graph;
  279. const stack = [0];
  280. const list = LinkedIndex(vCount);
  281. list.remove(0);
  282. while (stack.length > 0) {
  283. const v = stack.pop()!;
  284. const cIdx = componentIndex[v];
  285. for (let eI = offset[v], _eI = offset[v + 1]; eI < _eI; eI++) {
  286. const n = neighbor[eI];
  287. if (!list.has(n)) continue;
  288. list.remove(n);
  289. stack.push(n);
  290. componentIndex[n] = cIdx;
  291. }
  292. // check if we visited all vertices.
  293. // If not, create a new component and continue.
  294. if (stack.length === 0 && list.head >= 0) {
  295. stack.push(list.head);
  296. componentIndex[list.head] = ++currentComponent;
  297. list.remove(list.head);
  298. }
  299. }
  300. return { componentCount: vCount, componentIndex };
  301. }
  302. /**
  303. * Check if any vertex in `verticesA` is connected to any vertex in `verticesB`
  304. * via at most `maxDistance` edges.
  305. *
  306. * Returns true if verticesA and verticesB are intersecting.
  307. */
  308. export function areVertexSetsConnected(graph: IntAdjacencyGraph<any, any>, verticesA: SortedArray<number>, verticesB: SortedArray<number>, maxDistance: number): boolean {
  309. // check if A and B are intersecting, this handles maxDistance = 0
  310. if (SortedArray.areIntersecting(verticesA, verticesB)) return true;
  311. if (maxDistance < 1) return false;
  312. const visited = new Set<number>();
  313. for (let i = 0, il = verticesA.length; i < il; ++i) {
  314. visited.add(verticesA[i]);
  315. }
  316. return areVertexSetsConnectedImpl(graph, verticesA, verticesB, maxDistance, visited);
  317. }
  318. }
  319. function areVertexSetsConnectedImpl(graph: IntAdjacencyGraph<any, any>, frontier: ArrayLike<number>, target: SortedArray<number>, distance: number, visited: Set<number>): boolean {
  320. const { b: neighbor, offset } = graph;
  321. const newFrontier: number[] = [];
  322. for (let i = 0, il = frontier.length; i < il; ++i) {
  323. const src = frontier[i];
  324. for (let j = offset[src], jl = offset[src + 1]; j < jl; ++j) {
  325. const other = neighbor[j];
  326. if (visited.has(other)) continue;
  327. if (SortedArray.has(target, other)) return true;
  328. visited.add(other);
  329. newFrontier[newFrontier.length] = other;
  330. }
  331. }
  332. return distance > 1 ? areVertexSetsConnectedImpl(graph, newFrontier, target, distance - 1, visited) : false;
  333. }