int-adjacency-graph.ts 16 KB

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