From 3ae9803bf1b02ad516e6c26422f0f9471bb6df5f Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Thu, 26 Oct 2017 20:44:28 +0200 Subject: [PATCH] segment iterator --- src/mol-base/_spec/collections.spec.ts | 18 + src/mol-base/collections/iterator.ts | 2 - src/mol-base/collections/ordered-set.ts | 548 +----------------- src/mol-base/collections/ordered-set/base.ts | 515 ++++++++++++++++ .../ordered-set/segment-iterator.ts | 69 +++ src/mol-data/conformation.ts | 9 + src/mol-data/model.ts | 18 + src/mol-data/model/formats.ts | 8 + src/mol-data/model/interfaces/common.ts | 6 +- .../interfaces/{mmcif.ts => macromolecule.ts} | 6 +- src/perf-tests/sets.ts | 12 +- 11 files changed, 687 insertions(+), 524 deletions(-) create mode 100644 src/mol-base/collections/ordered-set/base.ts create mode 100644 src/mol-base/collections/ordered-set/segment-iterator.ts rename src/mol-data/model/interfaces/{mmcif.ts => macromolecule.ts} (72%) diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts index d8096b988..88c852cb3 100644 --- a/src/mol-base/_spec/collections.spec.ts +++ b/src/mol-base/_spec/collections.spec.ts @@ -275,6 +275,24 @@ describe('ordered set', () => { testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []); testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]); testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]); + + it('segments', () => { + const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); + const segs = OrderedSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]); + const it = OrderedSet.segments(segs, data); + + const t = Object.create(null); + for (let s = it.move(); !it.done; s = it.move()) { + for (let j = s.start; j < s.end; j++) { + const x = t[s.segmentIndex]; + const v = OrderedSet.getAt(data, j); + if (!x) t[s.segmentIndex] = [v]; + else x[x.length] = v; + } + } + + expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] }); + }); }); diff --git a/src/mol-base/collections/iterator.ts b/src/mol-base/collections/iterator.ts index c8e71eee2..8024ecde8 100644 --- a/src/mol-base/collections/iterator.ts +++ b/src/mol-base/collections/iterator.ts @@ -47,7 +47,6 @@ class ArrayIteratorImpl<T> implements Iterator<T> { this.index = -1; // try to avoid deoptimization with undefined values this.lastValue = xs.length > 0 ? xs[0] : void 0 as any; - return this; } } @@ -71,7 +70,6 @@ class RangeIteratorImpl implements Iterator<number> { constructor(private min: number, private max: number) { this.value = min - 1; this.done = max < min; - return this; } } diff --git a/src/mol-base/collections/ordered-set.ts b/src/mol-base/collections/ordered-set.ts index 726f6d238..c11f95280 100644 --- a/src/mol-base/collections/ordered-set.ts +++ b/src/mol-base/collections/ordered-set.ts @@ -4,534 +4,44 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import IntTuple from './int-tuple' -import { hash3, hash4 } from './hash-functions' - -/** An immutable ordered set. */ -interface OrderedSet { '@type': 'int-ordered-set' } +import * as Base from './ordered-set/base' +import SegmentIterator from './ordered-set/segment-iterator' namespace OrderedSet { - export const Empty: OrderedSet = IntTuple.pack(0, -1) as any; - export function ofSingleton(value: number): OrderedSet { return IntTuple.pack(value, value) as any; } - export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : IntTuple.pack(min, max) as any; } - /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ - export function ofSortedArray(xs: SortedArray): OrderedSet { - if (!xs.length) return Empty; - // check if the array is just a range - if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]); - return xs as any; - } - - export const has: (set: OrderedSet, x: number) => boolean = hasI as any; - export const indexOf: (set: OrderedSet, x: number) => number = indexOfI as any; - export const getAt: (set: OrderedSet, i: number) => number = getAtI as any; - - export const min: (set: OrderedSet) => number = minI as any; - export const max: (set: OrderedSet) => number = maxI as any; - export const size: (set: OrderedSet) => number = sizeI as any; - export const hashCode: (set: OrderedSet) => number = hashCodeI as any; - - export const areEqual: (a: OrderedSet, b: OrderedSet) => boolean = areEqualI as any; - export const areIntersecting: (a: OrderedSet, b: OrderedSet) => boolean = areIntersectingI as any; - export const isSubset: (a: OrderedSet, b: OrderedSet) => boolean = isSubsetI as any; - - export const union: (a: OrderedSet, b: OrderedSet) => OrderedSet = unionI as any; - export const intersect: (a: OrderedSet, b: OrderedSet) => OrderedSet = intersectI as any; - export const subtract: (a: OrderedSet, b: OrderedSet) => OrderedSet = subtractI as any; - - export const getInsertionIndex: (set: OrderedSet, x: number) => number = getInsertionIndexI as any; - export const getIntervalRange: (set: OrderedSet, min: number, max: number) => { start: number, end: number } = getIntervalRangeI as any; -} - -export default OrderedSet - -/** Long and painful implementation starts here */ - -type Range = IntTuple -type SortedArray = ArrayLike<number> -type OrderedSetImpl = Range | SortedArray - -function sizeI(set: OrderedSetImpl) { return typeof set === 'number' ? sizeR(set) : (set as SortedArray).length; } -function hasI(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set as SortedArray, x); } -function indexOfI(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set as SortedArray, x); } -function getAtI(set: OrderedSetImpl, i: number) { return typeof set === 'number' ? elementAtR(set, i) : (set as SortedArray)[i]; } -function minI(set: OrderedSetImpl) { return typeof set === 'number' ? minR(set) : (set as SortedArray)[0]; } -function maxI(set: OrderedSetImpl) { return typeof set === 'number' ? maxR(set) : (set as SortedArray)[(set as SortedArray).length - 1]; } - -function hashCodeI(set: OrderedSetImpl) { - // hash of tuple (size, min, max, mid) - const s = sizeI(set); - if (!s) return 0; - if (s > 2) return hash4(s, getAtI(set, 0), getAtI(set, s - 1), getAtI(set, s >> 1)); - return hash3(s, getAtI(set, 0), getAtI(set, s - 1)); -} -// TODO: possibly add more hash functions to allow for multilevel hashing. - -function areEqualI(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b); - return false; - } else if (typeof b === 'number') return false; - return equalAA(a as SortedArray, b as SortedArray); -} - -function areIntersectingI(a: OrderedSetImpl, b: OrderedSetImpl) { - // if at least one is "range", they must now intersect - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b) || areRangesIntersecting(a, b); - return areRangesIntersecting(a, b); - } - if (!areRangesIntersecting(a, b)) return false; - else if (typeof b === 'number') return false; - return areIntersectingAA(a as SortedArray, b as SortedArray); -} - -/** Check if the 2nd argument is a subset of the 1st */ -function isSubsetI(set: OrderedSetImpl, toTest: OrderedSetImpl) { - if (!isRangeSubset(set, toTest)) return false; - const testSize = sizeI(toTest); - if (typeof set === 'number' || !testSize) return true; - if (typeof toTest === 'number') return indexOfI(set, maxR(toTest)) - indexOfI(set, minR(toTest)) + 1 === testSize; - return isSubsetAA(set as SortedArray, toTest as SortedArray); -} - -function getInsertionIndexI(set: OrderedSetImpl, x: number) { - return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchIndex(set as SortedArray, x); -} - -function getIntervalRangeI(set: OrderedSetImpl, min: number, max: number) { - const { start, end } = getStartEnd(set, min, max); - return { start, end }; -} - -function unionI(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return unionRR(a, b); - return unionAR(b as SortedArray, a); - } else if (typeof b === 'number') { - return unionAR(a as SortedArray, b); - } else return unionAA(a as SortedArray, b as SortedArray); -} - -function intersectI(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return intersectRR(a, b); - return intersectAR(b as SortedArray, a); - } else if (typeof b === 'number') { - return intersectAR(a as SortedArray, b); - } else { - if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; - return intersectAA(a as SortedArray, b as SortedArray); - } -} - -function subtractI(a: OrderedSetImpl, b: OrderedSetImpl) { - if (!areRangesIntersecting(a, b)) return a; - - if (typeof a === 'number') { - if (typeof b === 'number') return substractRR(a, b); - return subtractRA(a, b as SortedArray); - } else if (typeof b === 'number') { - return subtractAR(a as SortedArray, b); - } else { - return subtractAA(a as SortedArray, b as SortedArray); - } -} - -const minR = IntTuple.fst -const maxR = IntTuple.snd -const equalRR = IntTuple.areEqual - -const _eR = IntTuple.zero(); -function sizeR(set: Range) { IntTuple.unpack(set, _eR); return _eR.snd - _eR.fst + 1; } -function hasR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd; } -function indexOfR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd ? x - _eR.fst : -1; } -function elementAtR(set: Range, i: number) { return IntTuple.fst(set) + i; } - -function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; } -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) { - 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++) { - if (value === xs[i]) return i; - } - return -1; - } - - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - return -1; -} - -function binarySearchIndex(xs: SortedArray, value: number) { - let min = 0, max = xs.length - 1; - while (min < max) { - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - if (min > max) return max + 1; - return xs[min] >= value ? min : min + 1; -} - -const _rsiR = IntTuple.zero(); -function rangeSearchIndex(r: Range, value: number) { - IntTuple.unpack(r, _rsiR); - if (value < _rsiR.fst) return 0; - if (value > _rsiR.snd) return _rsiR.snd - _rsiR.fst + 1; - return value - _rsiR.fst; -} - -const _maxIntRangeRet = { i: 0, j: 0, endA: 0, endB: 0 }; -function getMaxIntersectionRange(xs: SortedArray, ys: SortedArray) { - const la = xs.length - 1, lb = ys.length - 1; - _maxIntRangeRet.i = binarySearchIndex(xs, ys[0]); - _maxIntRangeRet.j = binarySearchIndex(ys, xs[0]); - _maxIntRangeRet.endA = Math.min(binarySearchIndex(xs, ys[lb]), la); - _maxIntRangeRet.endB = Math.min(binarySearchIndex(ys, xs[la]), lb); - return _maxIntRangeRet; -} - -const _startEndRet = { start: 0, end: 0 }; - -function getStartEnd(set: OrderedSetImpl, min: number, max: number) { - _startEndRet.start = getInsertionIndexI(set, min); - let end = getInsertionIndexI(set, max); - if (end < sizeI(set) && getAtI(set, end) === max) end++; - _startEndRet.end = end; - return _startEndRet; -} - -function equalAA(a: SortedArray, b: SortedArray) { - if (a === b) return true; - const size = a.length; - if (size !== b.length || a[0] !== b[0] || a[size - 1] !== b[size - 1]) return false; - for (let i = 0; i < size; i++) { - if (a[i] !== b[i]) return false; - } - return true; -} - -function areIntersectingAA(xs: SortedArray, ys: SortedArray) { - if (xs === ys) return true; - - let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); - while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else return true; - } - return false; -} - -function isSubsetAA(a: SortedArray, b: SortedArray) { - if (a === b) return true; + export interface IndexRange { start: number, end: number } + export function IndexRange(start?: number, end?: number): IndexRange { return { start: start || 0, end: end || 0 }; } - 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 = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; equal++; } - } - return equal === lenB; -} - -function areRangesIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { - return sizeI(a) > 0 && sizeI(b) > 0 && maxI(a) >= minI(b) && minI(a) <= maxI(b); -} - -function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) { - if (!sizeI(a)) return sizeI(b) === 0; - if (!sizeI(b)) return true; - return minI(a) <= minI(b) && maxI(a) >= maxI(b); -} - -function unionRR(a: Range, b: Range) { - if (IntTuple.areEqual(a, b)) return a; - - const sizeA = sizeR(a), sizeB = sizeR(b); - if (!sizeA) return b; - if (!sizeB) return a; - const minA = minR(a), minB = minR(b); - if (areRangesIntersecting(a, b)) return OrderedSet.ofRange(Math.min(minA, minB), Math.max(maxR(a), maxR(b))); - let lSize, lMin, rSize, rMin; - if (minR(a) < minR(b)) { lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; } - else { lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; } - const arr = new Int32Array(sizeA + sizeB); - for (let i = 0; i < lSize; i++) arr[i] = i + lMin; - for (let i = 0; i < rSize; i++) arr[i + lSize] = i + rMin; - return OrderedSet.ofSortedArray(arr); -} - -const _uAR = IntTuple.zero(); -function unionAR(a: SortedArray, b: Range) { - const bSize = sizeI(b); - if (!bSize) return a; - // is the array fully contained in the range? - if (isRangeSubset(b, a)) return b; - - IntTuple.unpack(b, _uAR); - const min = _uAR.fst, max = _uAR.snd; - const { start, end } = getStartEnd(a, min, max); - - const indices = new Int32Array(start + (a.length - end) + bSize); - let offset = 0; - for (let i = 0; i < start; i++) indices[offset++] = a[i]; - for (let i = min; i <= max; i++) indices[offset++] = i; - for (let i = end, _i = a.length; i < _i; i++) indices[offset] = a[i]; - - return OrderedSet.ofSortedArray(indices); -} - -function unionAA(a: SortedArray, b: SortedArray) { - if (a === b) return a; - - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - 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]); - // is this just a range? - if (resultSize === r - l + 1) { - return OrderedSet.ofRange(l, r); - } - - const indices = new Int32Array(lenA + lenB - commonCount); - let offset = 0; - - // insert the "prefixes" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - for (let k = 0; k < sJ; k++) indices[offset++] = b[k]; - - // insert the common part - i = sI; - j = sJ; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { indices[offset++] = y; j++; } - else { indices[offset++] = x; i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; - for (; j < lenB; j++) indices[offset++] = b[j]; - - return OrderedSet.ofSortedArray(indices); -} - -const _iRA = IntTuple.zero(), _iRB = IntTuple.zero(); -function intersectRR(a: Range, b: Range) { - if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; - if (IntTuple.areEqual(a, b)) return a; - - IntTuple.unpack(a, _iRA); - IntTuple.unpack(b, _iRB); - return OrderedSet.ofRange(Math.max(_iRA.fst, _iRB.fst), Math.min(_iRA.snd, _iRB.snd)); -} - -const _iAR = IntTuple.zero(); -function intersectAR(a: SortedArray, r: Range) { - if (!sizeI(r)) return OrderedSet.Empty; - - IntTuple.unpack(r, _iAR); - const { start, end } = getStartEnd(a, _iAR.fst, _iAR.snd); - const resultSize = end - start; - if (!resultSize) return OrderedSet.Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - for (let i = start; i < end; i++) { - indices[offset++] = a[i]; - } - return OrderedSet.ofSortedArray(indices); -} - -function intersectAA(a: SortedArray, b: SortedArray) { - if (a === b) return a; - - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - 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 = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = x; i++; j++; } - } - - return OrderedSet.ofSortedArray(indices); -} - -const _sRA = IntTuple.zero(), _sRB = IntTuple.zero(); -function substractRR(a: Range, b: Range) { - if (IntTuple.areEqual(a, b)) return OrderedSet.Empty; - - IntTuple.unpack(a, _sRA); - IntTuple.unpack(b, _sRB); - - if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a; - // is A subset of B? ==> Empty - if (isRangeSubset(b, a)) return OrderedSet.Empty; - 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; - if (l <= 0) return OrderedSet.ofRange(_sRB.snd + 1, _sRB.snd + r); - if (r <= 0) return OrderedSet.ofRange(_sRA.fst, _sRA.fst + l - 1); - const ret = new Int32Array(l + r); - let offset = 0; - for (let i = 0; i < l; i++) ret[offset++] = _sRA.fst + i; - for (let i = 1; i <= r; i++) ret[offset++] = _sRB.snd + i; - return OrderedSet.ofSortedArray(ret); - } - // non intersecting ranges are handled by top-level substract. - // at this point, b either contains rA.fst or rA.snd, but not both. - if (_sRA.fst < _sRB.fst) return OrderedSet.ofRange(_sRA.fst, _sRB.fst - 1); - return OrderedSet.ofRange(_sRB.snd + 1, _sRA.snd); -} - -const _sAR = IntTuple.zero(); -function subtractAR(a: SortedArray, b: Range) { - IntTuple.unpack(b, _sAR); - - // is empty? - if (_sAR.snd < _sAR.fst) return a; - - 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]; - for (let i = end, _i = a.length; i < _i; i++) ret[offset++] = a[i]; - return OrderedSet.ofSortedArray(ret); -} + export const Empty: OrderedSet = Base.Empty as any; + export const ofSingleton: (value: number) => OrderedSet = Base.ofSingleton as any; + export const ofRange: (min: number, max: number) => OrderedSet = Base.ofRange as any; + /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ + export const ofSortedArray: (xs: ArrayLike<number>) => OrderedSet = Base.ofSortedArray as any; -const _sAR1 = IntTuple.zero(); -function subtractRA(a: Range, b: SortedArray) { - IntTuple.unpack(a, _sAR1); + export const has: (set: OrderedSet, x: number) => boolean = Base.has as any; + export const indexOf: (set: OrderedSet, x: number) => number = Base.indexOf as any; + export const getAt: (set: OrderedSet, i: number) => number = Base.getAt as any; - // is empty? - if (_sAR1.snd < _sAR1.fst) return a; + export const min: (set: OrderedSet) => number = Base.minValue as any; + export const max: (set: OrderedSet) => number = Base.maxValue as any; + export const size: (set: OrderedSet) => number = Base.size as any; + export const hashCode: (set: OrderedSet) => number = Base.hashCode as any; - const min = _sAR1.fst, max = _sAR1.snd; - const rSize = max - min + 1; - const { start, end } = getStartEnd(b, min, max); - const commonCount = end - start; + export const areEqual: (a: OrderedSet, b: OrderedSet) => boolean = Base.areEqual as any; + export const areIntersecting: (a: OrderedSet, b: OrderedSet) => boolean = Base.areIntersecting as any; + export const isSubset: (a: OrderedSet, b: OrderedSet) => boolean = Base.isSubset as any; - // No common elements. - if (commonCount === 0) return a; + export const union: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.union as any; + export const intersect: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.intersect as any; + export const subtract: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.subtract as any; - const resultSize = rSize - commonCount; - // A is subset of B - if (resultSize <= 0) return OrderedSet.Empty; + export const getPredIndex: (set: OrderedSet, x: number) => number = Base.getPredIndex as any; + export const getPredIndex1: (set: OrderedSet, x: number, start: number, end: number) => number = Base.getPredIndex1 as any; + export const getIntervalRange: (set: OrderedSet, min: number, max: number) => IndexRange = (set, min, max) => Base.getIntervalRange(set as any, min, max, IndexRange()); + export const getIntervalRange1: (set: OrderedSet, min: number, max: number, target: IndexRange) => IndexRange = Base.getIntervalRange as any; - 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 (binarySearchRange(b, i, start, end) < 0) ret[offset++] = i; - } - for (let i = last + 1; i <= max; i++) ret[offset++] = i; - return OrderedSet.ofSortedArray(ret); + export const segments = SegmentIterator } -function subtractAA(a: SortedArray, b: SortedArray) { - if (a === b) return OrderedSet.Empty; - - const lenA = a.length; - - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - 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); - let offset = 0; - - // insert the "prefix" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - - i = sI; - j = sJ; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { j++; } - else { i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; +interface OrderedSet { '@type': 'int-ordered-set' } - return OrderedSet.ofSortedArray(indices); -} \ No newline at end of file +export default OrderedSet \ No newline at end of file diff --git a/src/mol-base/collections/ordered-set/base.ts b/src/mol-base/collections/ordered-set/base.ts new file mode 100644 index 000000000..ebfb97fb1 --- /dev/null +++ b/src/mol-base/collections/ordered-set/base.ts @@ -0,0 +1,515 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import IntTuple from '../int-tuple' +import { hash3, hash4 } from '../hash-functions' + +type Range = IntTuple +type SortedArray = ArrayLike<number> +type OrderedSetImpl = Range | SortedArray + +export const Empty: OrderedSetImpl = IntTuple.pack(0, -1) as any; +export function ofSingleton(value: number): OrderedSetImpl { return IntTuple.pack(value, value) as any; } +export function ofRange(min: number, max: number): OrderedSetImpl { return max < min ? Empty : IntTuple.pack(min, max) as any; } +/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ +export function ofSortedArray(xs: SortedArray): OrderedSetImpl { + if (!xs.length) return Empty; + // check if the array is just a range + if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]); + return xs as any; +} + +export function size(set: OrderedSetImpl) { return typeof set === 'number' ? sizeR(set) : (set as SortedArray).length; } +export function has(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set as SortedArray, x); } +export function indexOf(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set as SortedArray, x); } +export function getAt(set: OrderedSetImpl, i: number) { return typeof set === 'number' ? elementAtR(set, i) : (set as SortedArray)[i]; } +export function minValue(set: OrderedSetImpl) { return typeof set === 'number' ? minR(set) : (set as SortedArray)[0]; } +export function maxValue(set: OrderedSetImpl) { return typeof set === 'number' ? maxR(set) : (set as SortedArray)[(set as SortedArray).length - 1]; } + +export function hashCode(set: OrderedSetImpl) { + // hash of tuple (size, min, max, mid) + const s = size(set); + if (!s) return 0; + if (s > 2) return hash4(s, getAt(set, 0), getAt(set, s - 1), getAt(set, s >> 1)); + return hash3(s, getAt(set, 0), getAt(set, s - 1)); +} +// TODO: possibly add more hash functions to allow for multilevel hashing. + +export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return equalRR(a, b); + return false; + } else if (typeof b === 'number') return false; + return equalAA(a as SortedArray, b as SortedArray); +} + +export function areIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { + // if at least one is "range", they must now intersect + if (typeof a === 'number') { + if (typeof b === 'number') return equalRR(a, b) || areRangesIntersecting(a, b); + return areRangesIntersecting(a, b); + } + if (!areRangesIntersecting(a, b)) return false; + else if (typeof b === 'number') return false; + return areIntersectingAA(a as SortedArray, b as SortedArray); +} + +/** Check if the 2nd argument is a subset of the 1st */ +export function isSubset(set: OrderedSetImpl, toTest: OrderedSetImpl) { + if (!isRangeSubset(set, toTest)) return false; + const testSize = size(toTest); + if (typeof set === 'number' || !testSize) return true; + if (typeof toTest === 'number') return indexOf(set, maxR(toTest)) - indexOf(set, minR(toTest)) + 1 === testSize; + return isSubsetAA(set as SortedArray, toTest as SortedArray); +} + +export function getPredIndex(set: OrderedSetImpl, x: number) { + return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchPredIndex(set as SortedArray, x); +} + +export function getPredIndex1(set: OrderedSetImpl, x: number, start: number, end: number) { + return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchPredIndexRange(set as SortedArray, x, start, end); +} + +export function getIntervalRange(set: OrderedSetImpl, min: number, max: number, range: { start: number, end: number }) { + const { start, end } = getStartEnd(set, min, max); + range.start = start; + range.end = end; + return range; +} + +export function union(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return unionRR(a, b); + return unionAR(b as SortedArray, a); + } else if (typeof b === 'number') { + return unionAR(a as SortedArray, b); + } else return unionAA(a as SortedArray, b as SortedArray); +} + +export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return intersectRR(a, b); + return intersectAR(b as SortedArray, a); + } else if (typeof b === 'number') { + return intersectAR(a as SortedArray, b); + } else { + if (!areRangesIntersecting(a, b)) return Empty; + return intersectAA(a as SortedArray, b as SortedArray); + } +} + +export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) { + if (!areRangesIntersecting(a, b)) return a; + + if (typeof a === 'number') { + if (typeof b === 'number') return substractRR(a, b); + return subtractRA(a, b as SortedArray); + } else if (typeof b === 'number') { + return subtractAR(a as SortedArray, b); + } else { + return subtractAA(a as SortedArray, b as SortedArray); + } +} + +const minR = IntTuple.fst +const maxR = IntTuple.snd +const equalRR = IntTuple.areEqual + +const _eR = IntTuple.zero(); +function sizeR(set: Range) { IntTuple.unpack(set, _eR); return _eR.snd - _eR.fst + 1; } +function hasR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd; } +function indexOfR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd ? x - _eR.fst : -1; } +function elementAtR(set: Range, i: number) { return IntTuple.fst(set) + i; } + +function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; } +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) { + 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++) { + if (value === xs[i]) return i; + } + return -1; + } + + const mid = (min + max) >> 1; + const v = xs[mid]; + if (value < v) max = mid - 1; + else if (value > v) min = mid + 1; + else return mid; + } + return -1; +} + +function binarySearchPredIndex(xs: SortedArray, value: number) { + return binarySearchPredIndexRange(xs, value, 0, xs.length); +} + +function binarySearchPredIndexRange(xs: SortedArray, value: number, start: number, end: number) { + let min = start, max = end - 1; + while (min < max) { + const mid = (min + max) >> 1; + const v = xs[mid]; + if (value < v) max = mid - 1; + else if (value > v) min = mid + 1; + else return mid; + } + if (min > max) return max + 1; + return xs[min] >= value ? min : min + 1; +} + +const _rsiR = IntTuple.zero(); +function rangeSearchIndex(r: Range, value: number) { + IntTuple.unpack(r, _rsiR); + if (value < _rsiR.fst) return 0; + if (value > _rsiR.snd) return _rsiR.snd - _rsiR.fst + 1; + return value - _rsiR.fst; +} + +const _maxIntRangeRet = { i: 0, j: 0, endA: 0, endB: 0 }; +function getMaxIntersectionRange(xs: SortedArray, ys: SortedArray) { + const la = xs.length - 1, lb = ys.length - 1; + _maxIntRangeRet.i = binarySearchPredIndex(xs, ys[0]); + _maxIntRangeRet.j = binarySearchPredIndex(ys, xs[0]); + _maxIntRangeRet.endA = Math.min(binarySearchPredIndex(xs, ys[lb]), la); + _maxIntRangeRet.endB = Math.min(binarySearchPredIndex(ys, xs[la]), lb); + return _maxIntRangeRet; +} + +const _startEndRet = { start: 0, end: 0 }; +function getStartEnd(set: OrderedSetImpl, min: number, max: number) { + _startEndRet.start = getPredIndex(set, min); + _startEndRet.end = getPredIndex(set, max + 1); + return _startEndRet; +} + +function equalAA(a: SortedArray, b: SortedArray) { + if (a === b) return true; + const aSize = a.length; + if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false; + for (let i = 0; i < aSize; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} + +function areIntersectingAA(xs: SortedArray, ys: SortedArray) { + if (xs === ys) return true; + + let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); + while (i <= endA && j <= endB) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else return true; + } + return false; +} + +function isSubsetAA(a: SortedArray, b: SortedArray) { + if (a === b) return true; + + 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 = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; equal++; } + } + return equal === lenB; +} + +function areRangesIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { + return size(a) > 0 && size(b) > 0 && maxValue(a) >= minValue(b) && minValue(a) <= maxValue(b); +} + +function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) { + if (!size(a)) return size(b) === 0; + if (!size(b)) return true; + return minValue(a) <= minValue(b) && maxValue(a) >= maxValue(b); +} + +function unionRR(a: Range, b: Range) { + if (IntTuple.areEqual(a, b)) return a; + + const sizeA = sizeR(a), sizeB = sizeR(b); + if (!sizeA) return b; + if (!sizeB) return a; + const minA = minR(a), minB = minR(b); + if (areRangesIntersecting(a, b)) return ofRange(Math.min(minA, minB), Math.max(maxR(a), maxR(b))); + let lSize, lMin, rSize, rMin; + if (minR(a) < minR(b)) { lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; } + else { lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; } + const arr = new Int32Array(sizeA + sizeB); + for (let i = 0; i < lSize; i++) arr[i] = i + lMin; + for (let i = 0; i < rSize; i++) arr[i + lSize] = i + rMin; + return ofSortedArray(arr); +} + +const _uAR = IntTuple.zero(); +function unionAR(a: SortedArray, b: Range) { + const bSize = size(b); + if (!bSize) return a; + // is the array fully contained in the range? + if (isRangeSubset(b, a)) return b; + + IntTuple.unpack(b, _uAR); + const min = _uAR.fst, max = _uAR.snd; + const { start, end } = getStartEnd(a, min, max); + + const indices = new Int32Array(start + (a.length - end) + bSize); + let offset = 0; + for (let i = 0; i < start; i++) indices[offset++] = a[i]; + for (let i = min; i <= max; i++) indices[offset++] = i; + for (let i = end, _i = a.length; i < _i; i++) indices[offset] = a[i]; + + return ofSortedArray(indices); +} + +function unionAA(a: SortedArray, b: SortedArray) { + if (a === b) return a; + + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i <= endA && j <= endB) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + 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]); + // is this just a range? + if (resultSize === r - l + 1) { + return ofRange(l, r); + } + + const indices = new Int32Array(lenA + lenB - commonCount); + let offset = 0; + + // insert the "prefixes" + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; + for (let k = 0; k < sJ; k++) indices[offset++] = b[k]; + + // insert the common part + i = sI; + j = sJ; + while (i <= endA && j <= endB) { + const x = a[i], y = b[j]; + if (x < y) { indices[offset++] = x; i++; } + else if (x > y) { indices[offset++] = y; j++; } + else { indices[offset++] = x; i++; j++; } + } + + // insert the "tail" + for (; i < lenA; i++) indices[offset++] = a[i]; + for (; j < lenB; j++) indices[offset++] = b[j]; + + return ofSortedArray(indices); +} + +const _iRA = IntTuple.zero(), _iRB = IntTuple.zero(); +function intersectRR(a: Range, b: Range) { + if (!areRangesIntersecting(a, b)) return Empty; + if (IntTuple.areEqual(a, b)) return a; + + IntTuple.unpack(a, _iRA); + IntTuple.unpack(b, _iRB); + return ofRange(Math.max(_iRA.fst, _iRB.fst), Math.min(_iRA.snd, _iRB.snd)); +} + +const _iAR = IntTuple.zero(); +function intersectAR(a: SortedArray, r: Range) { + if (!size(r)) return Empty; + + IntTuple.unpack(r, _iAR); + const { start, end } = getStartEnd(a, _iAR.fst, _iAR.snd); + const resultSize = end - start; + if (!resultSize) return Empty; + + const indices = new Int32Array(resultSize); + let offset = 0; + for (let i = start; i < end; i++) { + indices[offset++] = a[i]; + } + return ofSortedArray(indices); +} + +function intersectAA(a: SortedArray, b: SortedArray) { + if (a === b) return a; + + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i <= endA && j <= endB) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = a.length, lenB = b.length; + // no common elements + if (!commonCount) return 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 = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { indices[offset++] = x; i++; j++; } + } + + return ofSortedArray(indices); +} + +const _sRA = IntTuple.zero(), _sRB = IntTuple.zero(); +function substractRR(a: Range, b: Range) { + if (IntTuple.areEqual(a, b)) return Empty; + + IntTuple.unpack(a, _sRA); + IntTuple.unpack(b, _sRB); + + if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a; + // is A subset of B? ==> Empty + if (isRangeSubset(b, a)) return Empty; + 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; + if (l <= 0) return ofRange(_sRB.snd + 1, _sRB.snd + r); + if (r <= 0) return ofRange(_sRA.fst, _sRA.fst + l - 1); + const ret = new Int32Array(l + r); + let offset = 0; + for (let i = 0; i < l; i++) ret[offset++] = _sRA.fst + i; + for (let i = 1; i <= r; i++) ret[offset++] = _sRB.snd + i; + return ofSortedArray(ret); + } + // non intersecting ranges are handled by top-level substract. + // at this point, b either contains rA.fst or rA.snd, but not both. + if (_sRA.fst < _sRB.fst) return ofRange(_sRA.fst, _sRB.fst - 1); + return ofRange(_sRB.snd + 1, _sRA.snd); +} + +const _sAR = IntTuple.zero(); +function subtractAR(a: SortedArray, b: Range) { + IntTuple.unpack(b, _sAR); + + // is empty? + if (_sAR.snd < _sAR.fst) return a; + + const min = _sAR.fst, max = _sAR.snd; + const { start, end } = getStartEnd(a, min, max); + const resultSize = a.length - (end - start); + // A is subset of B + if (resultSize <= 0) return Empty; + // No common elements + if (resultSize === a.length) return a; + + const ret = new Int32Array(resultSize); + let offset = 0; + for (let i = 0; i < start; i++) ret[offset++] = a[i]; + for (let i = end, _i = a.length; i < _i; i++) ret[offset++] = a[i]; + return ofSortedArray(ret); +} + +const _sAR1 = IntTuple.zero(); +function subtractRA(a: Range, b: SortedArray) { + IntTuple.unpack(a, _sAR1); + + // is empty? + if (_sAR1.snd < _sAR1.fst) return a; + + const min = _sAR1.fst, max = _sAR1.snd; + 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 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 (binarySearchRange(b, i, start, end) < 0) ret[offset++] = i; + } + for (let i = last + 1; i <= max; i++) ret[offset++] = i; + return ofSortedArray(ret); +} + +function subtractAA(a: SortedArray, b: SortedArray) { + if (a === b) return Empty; + + const lenA = a.length; + + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i <= endA && j <= endB) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + 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 Empty; + + const indices = new Int32Array(lenA - commonCount); + let offset = 0; + + // insert the "prefix" + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; + + i = sI; + j = sJ; + while (i <= endA && j <= endB) { + const x = a[i], y = b[j]; + if (x < y) { indices[offset++] = x; i++; } + else if (x > y) { j++; } + else { i++; j++; } + } + + // insert the "tail" + for (; i < lenA; i++) indices[offset++] = a[i]; + + return ofSortedArray(indices); +} \ No newline at end of file diff --git a/src/mol-base/collections/ordered-set/segment-iterator.ts b/src/mol-base/collections/ordered-set/segment-iterator.ts new file mode 100644 index 000000000..e9063f0ea --- /dev/null +++ b/src/mol-base/collections/ordered-set/segment-iterator.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Iterator from '../iterator' +import OrderedSet from '../ordered-set' + +class SegmentIterator implements Iterator<{ segmentIndex: number } & OrderedSet.IndexRange> { + private segmentRange = OrderedSet.IndexRange(); + private setRange = OrderedSet.IndexRange(); + private value = { segmentIndex: 0, start: 0, end: 0 }; + private last: number = 0; + + [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set); }; + done: boolean = false; + + next() { + const value = this.move(); + return { value, done: this.done } + } + + move() { + this.done = this.segmentRange.end <= this.segmentRange.start || this.setRange.end <= this.setRange.start; + while (!this.done) { + if (!this.updateValue()) { + this.updateSegmentRange(); + } else { + this.value.segmentIndex = this.segmentRange.start++; + break; + } + } + return this.value; + } + + private getSegmentIndex(value: number) { + if (value >= this.last) return -1; + return OrderedSet.getPredIndex(this.segments, value - 1); + } + + private updateValue() { + const segmentEnd = OrderedSet.getAt(this.segments, this.segmentRange.start + 1); + const end = OrderedSet.getPredIndex1(this.set, segmentEnd, this.setRange.start, this.setRange.end); + this.value.start = this.setRange.start; + this.value.end = end; + this.setRange.start = end; + return end > this.value.start; + } + + private updateSegmentRange() { + const min = OrderedSet.getAt(this.set, this.setRange.start), max = OrderedSet.getAt(this.set, this.setRange.end - 1); + this.segmentRange.start = this.getSegmentIndex(min); + this.segmentRange.end = this.getSegmentIndex(max) + 1; + this.done = this.segmentRange.end <= this.segmentRange.start || this.setRange.end <= this.setRange.start; + } + + constructor(private segments: OrderedSet, private set: OrderedSet) { + this.last = OrderedSet.max(segments); + this.setRange.end = OrderedSet.size(set); + this.updateSegmentRange(); + } +} + +function createIterator(segments: OrderedSet, set: OrderedSet) { + return new SegmentIterator(segments, set); +} + +export default createIterator \ No newline at end of file diff --git a/src/mol-data/conformation.ts b/src/mol-data/conformation.ts index bffdc60c4..c5ca79d16 100644 --- a/src/mol-data/conformation.ts +++ b/src/mol-data/conformation.ts @@ -4,7 +4,16 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +import OrderedSet from '../mol-base/collections/ordered-set' + interface Conformation { + x: ArrayLike<number>, + y: ArrayLike<number>, + z: ArrayLike<number>, + + // Assign a secondary structure type to each residue. + secondaryStructureType: ArrayLike<any>, + secondaryStructureAtomOffsets: OrderedSet } export default Conformation diff --git a/src/mol-data/model.ts b/src/mol-data/model.ts index af1d9e34b..8b52609a8 100644 --- a/src/mol-data/model.ts +++ b/src/mol-data/model.ts @@ -6,7 +6,25 @@ // TODO: define property accessor intefaces, graphs, spatial lookups and what have you. +import * as Formats from './model/formats' +import CommonInterface from './model/interfaces/common' +import MacromoleculeInterface from './model/interfaces/common' +import OrderedSet from '../mol-base/collections/ordered-set' + interface Model { + data: Formats.RawData, + + common: CommonInterface, + macromolecule: MacromoleculeInterface + + // Atom offsets of the "i-th chain" stored as a closed-open range [chainOffsets[i], chainOffsets[i + 1]) + chainSegments: OrderedSet, + // Atom offsets of the "i-th residue" stored as a closed-open range [residueOffsets[i], residueOffsets[i + 1]) + residueSegments: OrderedSet, + // Mapping from a residue index to chain index + residueChainIndex: ArrayLike<number>, + // Mapping from an atom into to residue index + atomResidueIndex: ArrayLike<number> } export default Model diff --git a/src/mol-data/model/formats.ts b/src/mol-data/model/formats.ts index e69de29bb..d0243888b 100644 --- a/src/mol-data/model/formats.ts +++ b/src/mol-data/model/formats.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + + +export type RawData = {} \ No newline at end of file diff --git a/src/mol-data/model/interfaces/common.ts b/src/mol-data/model/interfaces/common.ts index 36e049ca0..885f7c2f5 100644 --- a/src/mol-data/model/interfaces/common.ts +++ b/src/mol-data/model/interfaces/common.ts @@ -4,4 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -// TODO: define basic hierarchy, sec. structure. etc. \ No newline at end of file +interface Common { + +} + +export default Common \ No newline at end of file diff --git a/src/mol-data/model/interfaces/mmcif.ts b/src/mol-data/model/interfaces/macromolecule.ts similarity index 72% rename from src/mol-data/model/interfaces/mmcif.ts rename to src/mol-data/model/interfaces/macromolecule.ts index 3eae6edc1..368d780f2 100644 --- a/src/mol-data/model/interfaces/mmcif.ts +++ b/src/mol-data/model/interfaces/macromolecule.ts @@ -4,4 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -// TODO: map parts of mmCIF schema to create interfaces \ No newline at end of file +interface Macromolecule { + +} + +export default Macromolecule \ No newline at end of file diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 45e76cd5f..a35f15dd4 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -234,4 +234,14 @@ export namespace Build { } } -Build.run(); \ No newline at end of file +export function testSegments() { + const data = OrdSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); + const segs = OrdSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]); + const it = OrdSet.segments(segs, data); + + for (let s = it.move(); !it.done; s = it.move()) { + for (let j = s.start; j < s.end; j++) { + console.log(`${s.segmentIndex}: ${OrdSet.getAt(data, j)}`); + } + } +} \ No newline at end of file -- GitLab