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