diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts index ff3705c608f8da5d1ac19d23bb0cc3317dcc28f6..d8096b9880eee33a233d6a357af88b50750185f0 100644 --- a/src/mol-base/_spec/collections.spec.ts +++ b/src/mol-base/_spec/collections.spec.ts @@ -184,6 +184,7 @@ describe('ordered set', () => { expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 7]))).toBe(false); expect(OrderedSet.isSubset(OrderedSet.ofSortedArray([0, 1, 2, 3, 7, 10]), OrderedSet.ofSortedArray([1, 3, 7]))).toBe(true); expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 10, 45]))).toBe(false); + expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([12, 13, 16]))).toBe(false); }); it('access/membership', () => { @@ -235,6 +236,10 @@ describe('ordered set', () => { testEq('union AA', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); testEq('union AA1', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); testEq('union AA2', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 5, 6, 7])), [1, 2, 3, 4, 5, 6, 7]); + testEq('union AA3', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([2, 4])), [1, 2, 3, 4]); + testEq('union AA4', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([1, 3, 4])), [1, 3, 4]); + testEq('union AA5', OrderedSet.union(OrderedSet.ofSortedArray([1, 3, 4]), OrderedSet.ofSortedArray([1, 3])), [1, 3, 4]); + it('union AA6', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); testEq('intersect ES', OrderedSet.intersect(empty, singleton10), []); testEq('intersect ER', OrderedSet.intersect(empty, range1_4), []); @@ -246,6 +251,7 @@ describe('ordered set', () => { testEq('intersect RR2', OrderedSet.intersect(range1_4, OrderedSet.ofRange(3, 5)), [3, 4]); testEq('intersect RA', OrderedSet.intersect(range1_4, arr136), [1, 3]); testEq('intersect AA', OrderedSet.intersect(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); + it('intersect AA1', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); testEq('subtract ES', OrderedSet.subtract(empty, singleton10), []); testEq('subtract ER', OrderedSet.subtract(empty, range1_4), []); diff --git a/src/mol-base/collections/ordered-set.ts b/src/mol-base/collections/ordered-set.ts index c8ca6ac6fbdae4954662c0c4a49f98ea805347e9..c51ba055ac4ddb17c1acb54803872c2891d5e992 100644 --- a/src/mol-base/collections/ordered-set.ts +++ b/src/mol-base/collections/ordered-set.ts @@ -152,7 +152,11 @@ function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set. function indexOfA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] ? binarySearch(set, x) : -1; } function binarySearch(xs: SortedArray, value: number) { - let min = 0, max = xs.length - 1; + return binarySearchRange(xs, value, 0, xs.length); +} + +function binarySearchRange(xs: SortedArray, value: number, start: number, end: number) { + let min = start, max = end - 1; while (min <= max) { if (min + 11 > max) { for (let i = min; i <= max; i++) { @@ -234,17 +238,17 @@ function areIntersectingAA(xs: SortedArray, ys: SortedArray) { return false; } -function isSubsetAA(xs: SortedArray, ys: SortedArray) { - if (xs === ys) return true; +function isSubsetAA(a: SortedArray, b: SortedArray) { + if (a === b) return true; - const lenB = ys.length; - let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); - // the 2nd array must be able to advance by at least lenB elements - if (endB - j + 1 < lenB || endA - j + 1 < lenB) return false; + const lenB = b.length; + let { i, j, endA, endB } = getMaxIntersectionRange(a, b); + // must be able to advance by lenB elements + if (endB - j + 1 < lenB || endA - i + 1 < lenB) return false; let equal = 0; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { i++; } else if (x > y) { j++; } else { i++; j++; equal++; } @@ -302,8 +306,6 @@ function unionAR(a: SortedArray, b: Range) { function unionAA(a: SortedArray, b: SortedArray) { if (a === b) return a; - const lenA = a.length, lenB = b.length; - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); let i = sI, j = sJ; let commonCount = 0; @@ -314,8 +316,11 @@ function unionAA(a: SortedArray, b: SortedArray) { else { i++; j++; commonCount++; } } - if (!commonCount) return a; - if (commonCount >= lenA) return OrderedSet.Empty + const lenA = a.length, lenB = b.length; + // A === B || B is subset of A ==> A + if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a; + // A is subset of B ===> B + if (commonCount === lenA) return b; const resultSize = lenA + lenB - commonCount; const l = Math.min(a[0], b[0]), r = Math.max(a[lenA - 1], b[lenB - 1]); @@ -375,27 +380,33 @@ function intersectAR(a: SortedArray, r: Range) { return OrderedSet.ofSortedArray(indices); } -function intersectAA(xs: SortedArray, ys: SortedArray) { - if (xs === ys) return xs; +function intersectAA(a: SortedArray, b: SortedArray) { + if (a === b) return a; - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); let i = sI, j = sJ; - let resultSize = 0; + let commonCount = 0; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { i++; } else if (x > y) { j++; } - else { i++; j++; resultSize++; } + else { i++; j++; commonCount++; } } - if (!resultSize) return OrderedSet.Empty; - - const indices = new Int32Array(resultSize); + const lenA = a.length, lenB = b.length; + // no common elements + if (!commonCount) return OrderedSet.Empty; + // A === B || B is subset of A ==> B + if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b; + // A is subset of B ==> A + if (commonCount === lenA) return a; + + const indices = new Int32Array(commonCount); let offset = 0; i = sI; j = sJ; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { i++; } else if (x > y) { j++; } else { indices[offset++] = x; i++; j++; } @@ -417,6 +428,7 @@ function substractRR(a: Range, b: Range) { if (isRangeSubset(a, b)) { // this splits the interval into two, gotta represent it as a set. const l = _sRB.fst - _sRA.fst, r = _sRA.snd - _sRB.snd; + // TODO: check if l === 0 || r === 0 ==> result would be a range const ret = new Int32Array(l + r); let offset = 0; for (let i = 0; i < l; i++) ret[offset++] = _sRA.fst + i; @@ -439,7 +451,11 @@ function subtractAR(a: SortedArray, b: Range) { const min = _sAR.fst, max = _sAR.snd; const { start, end } = getStartEnd(a, min, max); const size = a.length - (end - start); + // A is subset of B if (size <= 0) return OrderedSet.Empty; + // No common elements + if (size === a.length) return a; + const ret = new Int32Array(size); let offset = 0; for (let i = 0; i < start; i++) ret[offset++] = a[i]; @@ -458,15 +474,21 @@ function subtractRA(a: Range, b: SortedArray) { const rSize = max - min + 1; const { start, end } = getStartEnd(b, min, max); const commonCount = end - start; + + // No common elements. + if (commonCount === 0) return a; + const resultSize = rSize - commonCount; + // A is subset of B if (resultSize <= 0) return OrderedSet.Empty; + const ret = new Int32Array(resultSize); const li = b.length - 1; const fst = b[Math.min(start, li)], last = b[Math.min(end, li)]; let offset = 0; for (let i = min; i < fst; i++) ret[offset++] = i; for (let i = fst; i <= last; i++) { - if (binarySearch(b, i) < 0) ret[offset++] = i; + if (binarySearchRange(b, i, start, end) < 0) ret[offset++] = i; } for (let i = last + 1; i <= max; i++) ret[offset++] = i; return OrderedSet.ofSortedArray(ret); @@ -487,7 +509,9 @@ function subtractAA(a: SortedArray, b: SortedArray) { else { i++; j++; commonCount++; } } + // A isnt intersecting B ===> A if (!commonCount) return a; + // A === B || A is subset of B ===> Empty if (commonCount >= lenA) return OrderedSet.Empty; const indices = new Int32Array(lenA - commonCount); diff --git a/src/mol-data/model.ts b/src/mol-data/model.ts index 0711c4f44de2dfcf15e02d407fbd32f263abb665..b941373a3038cc3e935514d1dd69c42daeb354d0 100644 --- a/src/mol-data/model.ts +++ b/src/mol-data/model.ts @@ -8,6 +8,11 @@ interface Model { + // Incremented when data changes + dataVersion: number, + + // Incremented when the underlying conformation changes + conformationVersion: number, } export default Model diff --git a/src/mol-data/structure.ts b/src/mol-data/structure.ts index 16b0d72f04b3d049343b06c2b1b1d66c64605627..37727d2497c437e5c38584d8dc5b0bb4b21981fe 100644 --- a/src/mol-data/structure.ts +++ b/src/mol-data/structure.ts @@ -21,7 +21,7 @@ export interface Unit extends Readonly<{ id: number, // Each unit can only contain atoms from a single "chain" - // the reason for this is to make symmetry and assembly transforms fast + // the reason for this is to make certain basic data transforms fast // without having to look at the actual contents of the unit // multiple units can point to the same chain chainIndex: number, @@ -30,7 +30,7 @@ export interface Unit extends Readonly<{ model: Model, // Determines the operation applied to this unit. - // The transform and and inverse a baked into the "getPosition" function + // The transform and and inverse are baked into the "getPosition" function operator: Operator }> { // returns the untransformed position. Used for spatial queries.