diff --git a/src/mol-data/atom-set.ts b/src/mol-data/atom-set.ts
index 9eb446f29b30facc70af75f63757af5509437944..fd25538be64eea75d812b1c5af5e092874d55dae 100644
--- a/src/mol-data/atom-set.ts
+++ b/src/mol-data/atom-set.ts
@@ -7,520 +7,41 @@
 import OrderedSet from '../mol-base/collections/ordered-set'
 import Iterator from '../mol-base/collections/iterator'
 import IntTuple from '../mol-base/collections/int-tuple'
-import { sortArray } from '../mol-base/collections/sort'
-import { hash1 } from '../mol-base/collections/hash-functions'
+import * as Base from './atom-set/base'
+import createBuilder from './atom-set/builder'
 
 /** A map-like representation of integer set */
-interface AtomSet { '@type': 'atom-set' }
-
 namespace AtomSet {
-    export const Empty: AtomSet = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty } as AtomSetElements as any;
-
-    export function create(data: IntTuple | ArrayLike<IntTuple> | IntTuple | { [id: number]: OrderedSet }): AtomSet {
-        if (typeof data === 'number') return data;
-        if (IntTuple.is(data)) return IntTuple.pack1(data) as any;
-        if (isArrayLike(data)) return ofTuples(data) as any;
-        return ofObject(data as { [id: number]: OrderedSet }) as any;
-    }
-
-    export const keys: (set: AtomSet) => OrderedSet = keysI as any;
-    export const keyCount: (set: AtomSet) => number = keyCountI as any;
-    export const hasKey: (set: AtomSet, key: number) => boolean = hasKeyI as any;
-    export const geyKey: (set: AtomSet, i: number) => number = getKeyI as any;
-    export const getByKey: (set: AtomSet, key: number) => OrderedSet = getByKeyI as any;
-    export const getByIndex: (set: AtomSet, i: number) => OrderedSet = getByIndexI as any;
-    export const has: (set: AtomSet, x: IntTuple) => boolean = hasI as any;
-    export const indexOf: (set: AtomSet, x: IntTuple) => number = indexOfI as any;
-    export const getAt: (set: AtomSet, i: number) => IntTuple = getAtI as any;
-    export const values: (set: AtomSet) => Iterator<IntTuple.Unpacked> = valuesI as any;
-
-    export const size: (set: AtomSet) => number = sizeI as any;
-    export const hashCode: (set: AtomSet) => number = hashCodeI as any;
-
-    export const areEqual: (a: AtomSet, b: AtomSet) => boolean = areEqualI as any;
-    export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = areIntersectingI as any;
-
-    export const union: (a: AtomSet, b: AtomSet) => AtomSet = unionI as any;
-    export const unionMany: (sets: AtomSet[]) => AtomSet = unionManyI as any;
-    export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = intersectI as any;
-    export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = subtractI as any;
-}
-
-export default AtomSet
-
-/** Long and painful implementation starts here */
-
-interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet }
-type AtomSetImpl = IntTuple | AtomSetElements
-
-function keysI(set: AtomSetImpl): OrderedSet {
-    if (typeof set === 'number') return OrderedSet.ofSingleton(set);
-    return (set as AtomSetElements).keys;
-}
+    export const Empty: AtomSet = Base.Empty as any;
 
-function keyCountI(set: AtomSetImpl): number {
-    if (typeof set === 'number') return 1;
-    return OrderedSet.size((set as AtomSetElements).keys);
-}
+    export const create: (data: IntTuple | ArrayLike<IntTuple> | IntTuple | { [id: number]: OrderedSet }) => AtomSet = Base.create as any;
 
-function hasKeyI(set: AtomSetImpl, key: number): boolean {
-    if (typeof set === 'number') return IntTuple.fst(set) === key;
-    return OrderedSet.has((set as AtomSetElements).keys, key);
-}
+    export const keys: (set: AtomSet) => OrderedSet = Base.getKeys as any;
+    export const keyCount: (set: AtomSet) => number = Base.keyCount as any;
+    export const hasKey: (set: AtomSet, key: number) => boolean = Base.hasKey as any;
+    export const geyKey: (set: AtomSet, i: number) => number = Base.getKey as any;
+    export const getByKey: (set: AtomSet, key: number) => OrderedSet = Base.getByKey as any;
+    export const getByIndex: (set: AtomSet, i: number) => OrderedSet = Base.getByIndex as any;
+    export const has: (set: AtomSet, x: IntTuple) => boolean = Base.hasTuple as any;
+    export const indexOf: (set: AtomSet, x: IntTuple) => number = Base.indexOf as any;
+    export const getAt: (set: AtomSet, i: number) => IntTuple = Base.getAt as any;
+    export const values: (set: AtomSet) => Iterator<IntTuple.Unpacked> = Base.values as any;
 
-function getKeyI(set: AtomSetImpl, index: number): number {
-    if (typeof set === 'number') return IntTuple.fst(set);
-    return OrderedSet.getAt((set as AtomSetElements).keys, index);
-}
+    export const size: (set: AtomSet) => number = Base.size as any;
+    export const hashCode: (set: AtomSet) => number = Base.hashCode as any;
 
-function hasI(set: AtomSetImpl, t: IntTuple): boolean {
-    if (typeof set === 'number') return IntTuple.areEqual(t, set);
-    IntTuple.unpack(t, _hasP);
-    return OrderedSet.has((set as AtomSetElements).keys, _hasP.fst) ? OrderedSet.has((set as AtomSetElements)[_hasP.fst], _hasP.snd) : false;
-}
-const _hasP = IntTuple.zero();
+    export const areEqual: (a: AtomSet, b: AtomSet) => boolean = Base.areEqual as any;
+    export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = Base.areIntersecting as any;
 
-function getByKeyI(set: AtomSetImpl, key: number): OrderedSet {
-    if (typeof set === 'number') {
-        IntTuple.unpack(set, _gS);
-        return _gS.fst === key ? OrderedSet.ofSingleton(_gS.snd) : OrderedSet.Empty;
-    }
-    return OrderedSet.has((set as AtomSetElements).keys, key) ? (set as AtomSetElements)[key] : OrderedSet.Empty;
-}
-const _gS = IntTuple.zero();
-
-function getByIndexI(set: AtomSetImpl, index: number): OrderedSet {
-    if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(IntTuple.snd(set)) : OrderedSet.Empty;
-    const key = OrderedSet.getAt((set as AtomSetElements).keys, index);
-    return (set as AtomSetElements)[key] || OrderedSet.Empty;
-}
-
-function getAtI(set: AtomSetImpl, i: number): IntTuple {
-    if (typeof set === 'number') return set;
-    return getAtE(set as AtomSetElements, i);
-}
-
-function indexOfI(set: AtomSetImpl, t: IntTuple) {
-    if (typeof set === 'number') return IntTuple.areEqual(set, t) ? 0 : -1;
-    return indexOfE(set as AtomSetElements, t);
-}
-
-/** Number elements in the "child" sets */
-function sizeI(set: AtomSetImpl) {
-    if (typeof set === 'number') return 1;
-    return (set as AtomSetElements).offsets[(set as AtomSetElements).offsets.length - 1];
-}
-
-function hashCodeI(set: AtomSetImpl) {
-    if (typeof set === 'number') return IntTuple.hashCode(set);
-    if ((set as AtomSetElements).hashCode !== -1) return (set as AtomSetElements).hashCode;
-    return computeHash((set as AtomSetElements));
-}
-
-function areEqualI(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return IntTuple.areEqual(a, b);
-        return false;
-    }
-    if (typeof b === 'number') return false;
-    return areEqualEE(a as AtomSetElements, b as AtomSetElements);
-}
-
-function areIntersectingI(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return IntTuple.areEqual(a, b);
-        return areIntersectingNE(a, b as AtomSetElements);
-    }
-    if (typeof b === 'number') return areIntersectingNE(b, a as AtomSetElements);
-    return areIntersectingEE(a as AtomSetElements, b as AtomSetElements);
-}
-
-function intersectI(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return IntTuple.areEqual(a, b) ? a : AtomSet.Empty;
-        return intersectNE(a, b as AtomSetElements);
-    }
-    if (typeof b === 'number') return intersectNE(b, a as AtomSetElements);
-    return intersectEE(a as AtomSetElements, b as AtomSetElements);
-}
-
-function subtractI(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return IntTuple.areEqual(a, b) ? AtomSet.Empty : a;
-        return subtractNE(a, b as AtomSetElements);
-    }
-    if (typeof b === 'number') return subtractEN(a as AtomSetElements, b);
-    return subtractEE(a as AtomSetElements, b as AtomSetElements);
-}
-
-function unionI(a: AtomSetImpl, b: AtomSetImpl) {
-    return findUnion([a, b]);
-}
-
-function unionManyI(sets: ArrayLike<AtomSetImpl>) {
-    return findUnion(sets);
-}
-
-class ElementsIterator implements Iterator<IntTuple.Unpacked> {
-    private pair = IntTuple.zero();
-
-    private keyCount: number;
-    private setIndex = -1;
-    private currentIndex = 0;
-    private currentSize = 0;
-    private currentSet: OrderedSet = OrderedSet.Empty;
-
-    [Symbol.iterator]() { return new ElementsIterator(this.elements); };
-    done: boolean;
-    next() { const value = this.move(); return { value, done: this.done } }
-
-    move() {
-        if (this.done) return this.pair;
-
-        if (this.currentIndex >= this.currentSize) {
-            if (!this.advance()) return this.pair;
-        }
-
-        this.pair.snd = OrderedSet.getAt(this.currentSet, this.currentIndex++);
-        return this.pair;
-    }
-
-    private advance() {
-        if (++this.setIndex >= this.keyCount) {
-            this.done = true;
-            return false;
-        }
-        const unit = OrderedSet.getAt(this.elements.keys, this.setIndex);
-        this.pair.fst = unit;
-        this.currentSet = this.elements[unit];
-        this.currentIndex = 0;
-        this.currentSize = OrderedSet.size(this.currentSet);
-        return true;
-    }
-
-    constructor(private elements: AtomSetElements) {
-        this.keyCount = OrderedSet.size(elements.keys);
-        this.done = this.keyCount === 0;
-        this.advance();
-    }
-}
-
-function valuesI(set: AtomSetImpl): Iterator<IntTuple.Unpacked> {
-    if (typeof set === 'number') return Iterator.Value(IntTuple.unpack1(set));
-    return new ElementsIterator(set as AtomSetElements);
-}
+    export const union: (a: AtomSet, b: AtomSet) => AtomSet = Base.union as any;
+    export const unionMany: (sets: AtomSet[]) => AtomSet = Base.unionMany as any;
+    export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Base.intersect as any;
+    export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Base.subtract as any;
 
-function isArrayLike(x: any): x is ArrayLike<IntTuple> {
-    return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer));
+    export function SortedBuilder(parent: AtomSet) { return createBuilder(parent, true); }
+    export function Builder(parent: AtomSet) { return createBuilder(parent, false); }
 }
 
-function ofObject(data: { [id: number]: OrderedSet }) {
-    const keys = [];
-    for (const _k of Object.keys(data)) {
-        const k = +_k;
-        if (OrderedSet.size(data[k]) > 0) keys[keys.length] = k;
-    }
-    if (!keys.length) return AtomSet.Empty;
-    if (keys.length === 1) {
-        const set = data[keys[0]];
-        if (OrderedSet.size(set) === 1) return IntTuple.pack(keys[0], OrderedSet.getAt(set, 0));
-    }
-    return ofObject1(keys, data);
-}
-
-function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) {
-    if (keys.length === 1) {
-        const k = keys[0];
-        const set = data[k];
-        if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
-    }
-    sortArray(keys);
-    return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data);
-}
-
-function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
-    if (OrderedSet.size(keys) === 1) {
-        const k = OrderedSet.getAt(keys, 0);
-        const set = data[k];
-        if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
-    }
-    return _createObjectOrdered(keys, data);
-}
-
-function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
-    const ret: AtomSetElements = Object.create(null);
-    ret.keys = keys;
-    const offsets = [0];
-    let size = 0;
-    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
-        const k = OrderedSet.getAt(keys, i);
-        const set = data[k];
-        ret[k] = set;
-        size += OrderedSet.size(set);
-        offsets[offsets.length] = size;
-    }
-    ret.offsets = offsets;
-    ret.hashCode = -1;
-    return ret;
-}
-
-function getUniqueElements(xs: number[]) {
-    let count = 1;
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] !== xs[i]) count++;
-    }
-    const ret = new (xs as any).constructor(count);
-    ret[0] = xs[0];
-    let offset = 1;
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i];
-    }
-    return ret;
-}
-
-function normalizeArray(xs: number[]) {
-    sortArray(xs);
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] === xs[i]) return getUniqueElements(xs);
-    }
-    return xs;
-}
-
-function ofTuples(xs: ArrayLike<IntTuple>) {
-    if (xs.length === 0) return AtomSet.Empty;
-    const sets: { [key: number]: number[] } = Object.create(null);
-    const p = IntTuple.zero();
-    for (let i = 0, _i = xs.length; i < _i; i++) {
-        IntTuple.unpack(xs[i], p);
-        const set = sets[p.fst];
-        if (set) set[set.length] = p.snd;
-        else sets[p.fst] = [p.snd];
-    }
-    const ret: { [key: number]: OrderedSet } = Object.create(null);
-    const keys = [];
-    for (const _k of Object.keys(sets)) {
-        const k = +_k;
-        keys[keys.length] = k;
-        ret[k] = OrderedSet.ofSortedArray(normalizeArray(sets[k]));
-    }
-    return ofObject1(keys, ret);
-}
-
-function getOffsetIndex(xs: ArrayLike<number>, 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;
-    }
-    return value < xs[min] ? min - 1 : min;
-}
-
-function getAtE(set: AtomSetElements, i: number): IntTuple {
-    const { offsets, keys } = set;
-    const o = getOffsetIndex(offsets, i);
-    if (o >= offsets.length - 1) return 0 as any;
-    const k = OrderedSet.getAt(keys, o);
-    const e = OrderedSet.getAt(set[k], i - offsets[o]);
-    return IntTuple.pack(k, e);
-}
-
-const _iOE = IntTuple.zero();
-function indexOfE(set: AtomSetElements, t: IntTuple) {
-    IntTuple.unpack(t, _iOE);
-    const { keys } = set;
-    const setIdx = OrderedSet.indexOf(keys, _iOE.fst);
-    if (setIdx < 0) return -1;
-    const o = OrderedSet.indexOf(set[_iOE.fst], _iOE.snd);
-    if (o < 0) return -1;
-    return set.offsets[setIdx] + o;
-}
-
-function computeHash(set: AtomSetElements) {
-    const { keys } = set;
-    let hash = 23;
-    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
-        const k = OrderedSet.getAt(keys, i);
-        hash = (31 * hash + k) | 0;
-        hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0;
-    }
-    hash = (31 * hash + sizeI(set)) | 0;
-    hash = hash1(hash);
-    set.hashCode = hash;
-    return hash;
-}
-
-function areEqualEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return true;
-    if (sizeI(a) !== sizeI(a)) return false;
-
-    const keys = a.keys;
-    if (!OrderedSet.areEqual(keys, b.keys)) return false;
-    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
-        const k = OrderedSet.getAt(keys, i);
-        if (!OrderedSet.areEqual(a[k], b[k])) return false;
-    }
-    return true;
-}
-
-const _aeP = IntTuple.zero();
-function areIntersectingNE(a: IntTuple, b: AtomSetElements) {
-    IntTuple.unpack(a, _aeP);
-    return OrderedSet.has(b.keys, _aeP.fst) && OrderedSet.has(b[_aeP.fst], _aeP.snd);
-}
-
-function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return true;
-    const keysA = a.keys, keysB = b.keys;
-    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return false;
-    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
-    for (let i = start; i < end; i++) {
-        const k = OrderedSet.getAt(keysA, i);
-        if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true;
-    }
-    return false;
-}
-
-const _nP = IntTuple.zero();
-function intersectNE(a: IntTuple, b: AtomSetElements) {
-    IntTuple.unpack(a, _nP);
-    return OrderedSet.has(b.keys, _nP.fst) && OrderedSet.has(b[_nP.fst], _nP.snd) ? a : AtomSet.Empty;
-}
-
-function intersectEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return a;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return AtomSet.Empty;
-    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
-
-    const keys = [], ret = Object.create(null);
-    for (let i = start; i < end; i++) {
-        const k = OrderedSet.getAt(keysA, i);
-        if (OrderedSet.has(keysB, k)) {
-            const intersection = OrderedSet.intersect(a[k], b[k]);
-            if (OrderedSet.size(intersection) > 0) {
-                keys[keys.length] = k;
-                ret[k] = intersection;
-            }
-        }
-    }
-    return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret);
-}
-
-const _sNE = IntTuple.zero();
-function subtractNE(a: IntTuple, b: AtomSetElements) {
-    IntTuple.unpack(a, _sNE);
-    return OrderedSet.has(b.keys, _sNE.fst) && OrderedSet.has(b[_sNE.fst], _sNE.snd) ? AtomSet.Empty : a;
-}
-
-const _sEN = IntTuple.zero();
-function subtractEN(a: AtomSetElements, b: IntTuple): AtomSetImpl {
-    const aKeys =  a.keys;
-    IntTuple.unpack(b, _sEN);
-    if (!OrderedSet.has(aKeys, _sEN.fst) || !OrderedSet.has(a[_sEN.fst], _sEN.snd)) return a;
-    const set = a[_sEN.fst];
-    if (OrderedSet.size(set) === 1) {
-        return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(_sEN.fst)), a);
-    } else {
-        return ofObjectOrdered(OrderedSet.subtract(set, OrderedSet.ofSingleton(_sEN.snd)), a);
-    }
-}
-
-function subtractEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return AtomSet.Empty;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return AtomSet.Empty;
-    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
-
-    const keys = [], ret = Object.create(null);
-    for (let i = 0; i < start; i++) {
-        const k = OrderedSet.getAt(keysA, i);
-        keys[keys.length] = k;
-        ret[k] = a[k];
-    }
-    for (let i = start; i < end; i++) {
-        const k = OrderedSet.getAt(keysA, i);
-        if (OrderedSet.has(keysB, k)) {
-            const subtraction = OrderedSet.subtract(a[k], b[k]);
-            if (OrderedSet.size(subtraction) > 0) {
-                keys[keys.length] = k;
-                ret[k] = subtraction;
-            }
-        } else {
-            keys[keys.length] = k;
-            ret[k] = a[k];
-        }
-    }
-    for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) {
-        const k = OrderedSet.getAt(keysA, i);
-        keys[keys.length] = k;
-        ret[k] = a[k];
-    }
-    return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret);
-}
-
-function findUnion(sets: ArrayLike<AtomSetImpl>) {
-    if (!sets.length) return AtomSet.Empty;
-    if (sets.length === 1) return sets[0];
-    if (sets.length === 2 && areEqualI(sets[0], sets[1])) return sets[0];
-
-    const eCount = { count: 0 };
-    const ns = unionN(sets, eCount);
-    if (!eCount.count) return ns;
-    const ret = Object.create(null);
-    for (let i = 0, _i = sets.length; i < _i; i++) {
-        const s = sets[i];
-        if (typeof s !== 'number') unionInto(ret, s as AtomSetElements);
-    }
-    if (sizeI(ns as AtomSetImpl) > 0) {
-        if (typeof ns === 'number') unionIntoN(ret, ns as any);
-        else unionInto(ret, ns as AtomSetElements);
-    }
-    return ofObject(ret);
-}
-
-function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) {
-    let countN = 0, countE = 0;
-    for (let i = 0, _i = sets.length; i < _i; i++) {
-        if (typeof sets[i] === 'number') countN++;
-        else countE++;
-    }
-    eCount.count = countE;
-    if (!countN) return AtomSet.Empty;
-    if (countN === sets.length) return ofTuples(sets as ArrayLike<IntTuple>);
-    const packed = new Float64Array(countN);
-    let offset = 0;
-    for (let i = 0, _i = sets.length; i < _i; i++) {
-        const s = sets[i];
-        if (typeof s === 'number') packed[offset++] = s;
-    }
-    return ofTuples(packed as any);
-}
-
-function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) {
-    const keys = a.keys;
-    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
-        const k = OrderedSet.getAt(keys, i);
-        const set = data[k];
-        if (set) data[k] = OrderedSet.union(set, a[k]);
-        else data[k] = a[k];
-    }
-}
+interface AtomSet { '@type': 'atom-set' }
 
-const _uIN = IntTuple.zero();
-function unionIntoN(data: { [key: number]: OrderedSet }, a: IntTuple) {
-    IntTuple.unpack(a, _uIN);
-    const set = data[_uIN.fst];
-    if (set) {
-        data[_uIN.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(_uIN.snd));
-    } else {
-        data[_uIN.fst] = OrderedSet.ofSingleton(_uIN.snd);
-    }
-}
\ No newline at end of file
+export default AtomSet
\ No newline at end of file
diff --git a/src/mol-data/atom-set/base.ts b/src/mol-data/atom-set/base.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2be02c26e1e4c43cb1be15928b31760c99537b92
--- /dev/null
+++ b/src/mol-data/atom-set/base.ts
@@ -0,0 +1,501 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import OrderedSet from '../../mol-base/collections/ordered-set'
+import Iterator from '../../mol-base/collections/iterator'
+import IntTuple from '../../mol-base/collections/int-tuple'
+import { sortArray } from '../../mol-base/collections/sort'
+import { hash1 } from '../../mol-base/collections/hash-functions'
+
+/** Long and painful implementation starts here */
+
+export interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet }
+export type AtomSetImpl = IntTuple | AtomSetElements
+
+export const Empty: AtomSetImpl = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty };
+
+export function create(data: IntTuple | ArrayLike<IntTuple> | IntTuple | { [id: number]: OrderedSet }): AtomSetImpl {
+    if (typeof data === 'number') return data;
+    if (IntTuple.is(data)) return IntTuple.pack1(data);
+    if (isArrayLike(data)) return ofTuples(data);
+    return ofObject(data as { [id: number]: OrderedSet });
+}
+
+export function isSingleton(set: AtomSetImpl) {
+    return typeof set === 'number';
+}
+
+export function getKeys(set: AtomSetImpl): OrderedSet {
+    if (typeof set === 'number') return OrderedSet.ofSingleton(set);
+    return (set as AtomSetElements).keys;
+}
+
+export function keyCount(set: AtomSetImpl): number {
+    if (typeof set === 'number') return 1;
+    return OrderedSet.size((set as AtomSetElements).keys);
+}
+
+export function hasKey(set: AtomSetImpl, key: number): boolean {
+    if (typeof set === 'number') return IntTuple.fst(set) === key;
+    return OrderedSet.has((set as AtomSetElements).keys, key);
+}
+
+export function getKey(set: AtomSetImpl, index: number): number {
+    if (typeof set === 'number') return IntTuple.fst(set);
+    return OrderedSet.getAt((set as AtomSetElements).keys, index);
+}
+
+export function hasTuple(set: AtomSetImpl, t: IntTuple): boolean {
+    if (typeof set === 'number') return IntTuple.areEqual(t, set);
+    IntTuple.unpack(t, _hasP);
+    return OrderedSet.has((set as AtomSetElements).keys, _hasP.fst) ? OrderedSet.has((set as AtomSetElements)[_hasP.fst], _hasP.snd) : false;
+}
+const _hasP = IntTuple.zero();
+
+export function getByKey(set: AtomSetImpl, key: number): OrderedSet {
+    if (typeof set === 'number') {
+        IntTuple.unpack(set, _gS);
+        return _gS.fst === key ? OrderedSet.ofSingleton(_gS.snd) : OrderedSet.Empty;
+    }
+    return OrderedSet.has((set as AtomSetElements).keys, key) ? (set as AtomSetElements)[key] : OrderedSet.Empty;
+}
+const _gS = IntTuple.zero();
+
+export function getByIndex(set: AtomSetImpl, index: number): OrderedSet {
+    if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(IntTuple.snd(set)) : OrderedSet.Empty;
+    const key = OrderedSet.getAt((set as AtomSetElements).keys, index);
+    return (set as AtomSetElements)[key] || OrderedSet.Empty;
+}
+
+export function getAt(set: AtomSetImpl, i: number): IntTuple {
+    if (typeof set === 'number') return set;
+    return getAtE(set as AtomSetElements, i);
+}
+
+export function indexOf(set: AtomSetImpl, t: IntTuple) {
+    if (typeof set === 'number') return IntTuple.areEqual(set, t) ? 0 : -1;
+    return indexOfE(set as AtomSetElements, t);
+}
+
+/** Number elements in the "child" sets */
+export function size(set: AtomSetImpl) {
+    if (typeof set === 'number') return 1;
+    return (set as AtomSetElements).offsets[(set as AtomSetElements).offsets.length - 1];
+}
+
+export function hashCode(set: AtomSetImpl) {
+    if (typeof set === 'number') return IntTuple.hashCode(set);
+    if ((set as AtomSetElements).hashCode !== -1) return (set as AtomSetElements).hashCode;
+    return computeHash((set as AtomSetElements));
+}
+
+export function areEqual(a: AtomSetImpl, b: AtomSetImpl) {
+    if (typeof a === 'number') {
+        if (typeof b === 'number') return IntTuple.areEqual(a, b);
+        return false;
+    }
+    if (typeof b === 'number') return false;
+    return areEqualEE(a as AtomSetElements, b as AtomSetElements);
+}
+
+export function areIntersecting(a: AtomSetImpl, b: AtomSetImpl) {
+    if (typeof a === 'number') {
+        if (typeof b === 'number') return IntTuple.areEqual(a, b);
+        return areIntersectingNE(a, b as AtomSetElements);
+    }
+    if (typeof b === 'number') return areIntersectingNE(b, a as AtomSetElements);
+    return areIntersectingEE(a as AtomSetElements, b as AtomSetElements);
+}
+
+export function intersect(a: AtomSetImpl, b: AtomSetImpl) {
+    if (typeof a === 'number') {
+        if (typeof b === 'number') return IntTuple.areEqual(a, b) ? a : Empty;
+        return intersectNE(a, b as AtomSetElements);
+    }
+    if (typeof b === 'number') return intersectNE(b, a as AtomSetElements);
+    return intersectEE(a as AtomSetElements, b as AtomSetElements);
+}
+
+export function subtract(a: AtomSetImpl, b: AtomSetImpl) {
+    if (typeof a === 'number') {
+        if (typeof b === 'number') return IntTuple.areEqual(a, b) ? Empty : a;
+        return subtractNE(a, b as AtomSetElements);
+    }
+    if (typeof b === 'number') return subtractEN(a as AtomSetElements, b);
+    return subtractEE(a as AtomSetElements, b as AtomSetElements);
+}
+
+export function union(a: AtomSetImpl, b: AtomSetImpl) {
+    return findUnion([a, b]);
+}
+
+export function unionMany(sets: ArrayLike<AtomSetImpl>) {
+    return findUnion(sets);
+}
+
+class ElementsIterator implements Iterator<IntTuple.Unpacked> {
+    private pair = IntTuple.zero();
+
+    private keyCount: number;
+    private setIndex = -1;
+    private currentIndex = 0;
+    private currentSize = 0;
+    private currentSet: OrderedSet = OrderedSet.Empty;
+
+    [Symbol.iterator]() { return new ElementsIterator(this.elements); };
+    done: boolean;
+    next() { const value = this.move(); return { value, done: this.done } }
+
+    move() {
+        if (this.done) return this.pair;
+
+        if (this.currentIndex >= this.currentSize) {
+            if (!this.advance()) return this.pair;
+        }
+
+        this.pair.snd = OrderedSet.getAt(this.currentSet, this.currentIndex++);
+        return this.pair;
+    }
+
+    private advance() {
+        if (++this.setIndex >= this.keyCount) {
+            this.done = true;
+            return false;
+        }
+        const unit = OrderedSet.getAt(this.elements.keys, this.setIndex);
+        this.pair.fst = unit;
+        this.currentSet = this.elements[unit];
+        this.currentIndex = 0;
+        this.currentSize = OrderedSet.size(this.currentSet);
+        return true;
+    }
+
+    constructor(private elements: AtomSetElements) {
+        this.keyCount = OrderedSet.size(elements.keys);
+        this.done = this.keyCount === 0;
+        this.advance();
+    }
+}
+
+export function values(set: AtomSetImpl): Iterator<IntTuple.Unpacked> {
+    if (typeof set === 'number') return Iterator.Value(IntTuple.unpack1(set));
+    return new ElementsIterator(set as AtomSetElements);
+}
+
+function isArrayLike(x: any): x is ArrayLike<IntTuple> {
+    return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer));
+}
+
+function ofObject(data: { [id: number]: OrderedSet }) {
+    const keys = [];
+    for (const _k of Object.keys(data)) {
+        const k = +_k;
+        if (OrderedSet.size(data[k]) > 0) keys[keys.length] = k;
+    }
+    if (!keys.length) return Empty;
+    if (keys.length === 1) {
+        const set = data[keys[0]];
+        if (OrderedSet.size(set) === 1) return IntTuple.pack(keys[0], OrderedSet.getAt(set, 0));
+    }
+    return ofObject1(keys, data);
+}
+
+function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) {
+    if (keys.length === 1) {
+        const k = keys[0];
+        const set = data[k];
+        if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
+    }
+    sortArray(keys);
+    return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data);
+}
+
+function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
+    if (OrderedSet.size(keys) === 1) {
+        const k = OrderedSet.getAt(keys, 0);
+        const set = data[k];
+        if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
+    }
+    return _createObjectOrdered(keys, data);
+}
+
+function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
+    const ret: AtomSetElements = Object.create(null);
+    ret.keys = keys;
+    const offsets = [0];
+    let runningSize = 0;
+    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
+        const k = OrderedSet.getAt(keys, i);
+        const set = data[k];
+        ret[k] = set;
+        runningSize += OrderedSet.size(set);
+        offsets[offsets.length] = runningSize;
+    }
+    ret.offsets = offsets;
+    ret.hashCode = -1;
+    return ret;
+}
+
+function getUniqueElements(xs: number[]) {
+    let count = 1;
+    for (let i = 1, _i = xs.length; i < _i; i++) {
+        if (xs[i - 1] !== xs[i]) count++;
+    }
+    const ret = new (xs as any).constructor(count);
+    ret[0] = xs[0];
+    let offset = 1;
+    for (let i = 1, _i = xs.length; i < _i; i++) {
+        if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i];
+    }
+    return ret;
+}
+
+function normalizeArray(xs: number[]) {
+    sortArray(xs);
+    for (let i = 1, _i = xs.length; i < _i; i++) {
+        if (xs[i - 1] === xs[i]) return getUniqueElements(xs);
+    }
+    return xs;
+}
+
+function ofTuples(xs: ArrayLike<IntTuple>) {
+    if (xs.length === 0) return Empty;
+    const sets: { [key: number]: number[] } = Object.create(null);
+    const p = IntTuple.zero();
+    for (let i = 0, _i = xs.length; i < _i; i++) {
+        IntTuple.unpack(xs[i], p);
+        const set = sets[p.fst];
+        if (set) set[set.length] = p.snd;
+        else sets[p.fst] = [p.snd];
+    }
+    const ret: { [key: number]: OrderedSet } = Object.create(null);
+    const keys = [];
+    for (const _k of Object.keys(sets)) {
+        const k = +_k;
+        keys[keys.length] = k;
+        ret[k] = OrderedSet.ofSortedArray(normalizeArray(sets[k]));
+    }
+    return ofObject1(keys, ret);
+}
+
+function getOffsetIndex(xs: ArrayLike<number>, 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;
+    }
+    return value < xs[min] ? min - 1 : min;
+}
+
+function getAtE(set: AtomSetElements, i: number): IntTuple {
+    const { offsets, keys } = set;
+    const o = getOffsetIndex(offsets, i);
+    if (o >= offsets.length - 1) return 0 as any;
+    const k = OrderedSet.getAt(keys, o);
+    const e = OrderedSet.getAt(set[k], i - offsets[o]);
+    return IntTuple.pack(k, e);
+}
+
+const _iOE = IntTuple.zero();
+function indexOfE(set: AtomSetElements, t: IntTuple) {
+    IntTuple.unpack(t, _iOE);
+    const { keys } = set;
+    const setIdx = OrderedSet.indexOf(keys, _iOE.fst);
+    if (setIdx < 0) return -1;
+    const o = OrderedSet.indexOf(set[_iOE.fst], _iOE.snd);
+    if (o < 0) return -1;
+    return set.offsets[setIdx] + o;
+}
+
+function computeHash(set: AtomSetElements) {
+    const { keys } = set;
+    let hash = 23;
+    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
+        const k = OrderedSet.getAt(keys, i);
+        hash = (31 * hash + k) | 0;
+        hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0;
+    }
+    hash = (31 * hash + size(set)) | 0;
+    hash = hash1(hash);
+    set.hashCode = hash;
+    return hash;
+}
+
+function areEqualEE(a: AtomSetElements, b: AtomSetElements) {
+    if (a === b) return true;
+    if (size(a) !== size(a)) return false;
+
+    const keys = a.keys;
+    if (!OrderedSet.areEqual(keys, b.keys)) return false;
+    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
+        const k = OrderedSet.getAt(keys, i);
+        if (!OrderedSet.areEqual(a[k], b[k])) return false;
+    }
+    return true;
+}
+
+const _aeP = IntTuple.zero();
+function areIntersectingNE(a: IntTuple, b: AtomSetElements) {
+    IntTuple.unpack(a, _aeP);
+    return OrderedSet.has(b.keys, _aeP.fst) && OrderedSet.has(b[_aeP.fst], _aeP.snd);
+}
+
+function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
+    if (a === b) return true;
+    const keysA = a.keys, keysB = b.keys;
+    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return false;
+    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
+    for (let i = start; i < end; i++) {
+        const k = OrderedSet.getAt(keysA, i);
+        if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true;
+    }
+    return false;
+}
+
+const _nP = IntTuple.zero();
+function intersectNE(a: IntTuple, b: AtomSetElements) {
+    IntTuple.unpack(a, _nP);
+    return OrderedSet.has(b.keys, _nP.fst) && OrderedSet.has(b[_nP.fst], _nP.snd) ? a : Empty;
+}
+
+function intersectEE(a: AtomSetElements, b: AtomSetElements) {
+    if (a === b) return a;
+
+    const keysA = a.keys, keysB = b.keys;
+    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty;
+    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
+
+    const keys = [], ret = Object.create(null);
+    for (let i = start; i < end; i++) {
+        const k = OrderedSet.getAt(keysA, i);
+        if (OrderedSet.has(keysB, k)) {
+            const intersection = OrderedSet.intersect(a[k], b[k]);
+            if (OrderedSet.size(intersection) > 0) {
+                keys[keys.length] = k;
+                ret[k] = intersection;
+            }
+        }
+    }
+    return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret);
+}
+
+const _sNE = IntTuple.zero();
+function subtractNE(a: IntTuple, b: AtomSetElements) {
+    IntTuple.unpack(a, _sNE);
+    return OrderedSet.has(b.keys, _sNE.fst) && OrderedSet.has(b[_sNE.fst], _sNE.snd) ? Empty : a;
+}
+
+const _sEN = IntTuple.zero();
+function subtractEN(a: AtomSetElements, b: IntTuple): AtomSetImpl {
+    const aKeys =  a.keys;
+    IntTuple.unpack(b, _sEN);
+    if (!OrderedSet.has(aKeys, _sEN.fst) || !OrderedSet.has(a[_sEN.fst], _sEN.snd)) return a;
+    const set = a[_sEN.fst];
+    if (OrderedSet.size(set) === 1) {
+        return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(_sEN.fst)), a);
+    } else {
+        return ofObjectOrdered(OrderedSet.subtract(set, OrderedSet.ofSingleton(_sEN.snd)), a);
+    }
+}
+
+function subtractEE(a: AtomSetElements, b: AtomSetElements) {
+    if (a === b) return Empty;
+
+    const keysA = a.keys, keysB = b.keys;
+    if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty;
+    const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB));
+
+    const keys = [], ret = Object.create(null);
+    for (let i = 0; i < start; i++) {
+        const k = OrderedSet.getAt(keysA, i);
+        keys[keys.length] = k;
+        ret[k] = a[k];
+    }
+    for (let i = start; i < end; i++) {
+        const k = OrderedSet.getAt(keysA, i);
+        if (OrderedSet.has(keysB, k)) {
+            const subtraction = OrderedSet.subtract(a[k], b[k]);
+            if (OrderedSet.size(subtraction) > 0) {
+                keys[keys.length] = k;
+                ret[k] = subtraction;
+            }
+        } else {
+            keys[keys.length] = k;
+            ret[k] = a[k];
+        }
+    }
+    for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) {
+        const k = OrderedSet.getAt(keysA, i);
+        keys[keys.length] = k;
+        ret[k] = a[k];
+    }
+    return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret);
+}
+
+function findUnion(sets: ArrayLike<AtomSetImpl>) {
+    if (!sets.length) return Empty;
+    if (sets.length === 1) return sets[0];
+    if (sets.length === 2 && areEqual(sets[0], sets[1])) return sets[0];
+
+    const eCount = { count: 0 };
+    const ns = unionN(sets, eCount);
+    if (!eCount.count) return ns;
+    const ret = Object.create(null);
+    for (let i = 0, _i = sets.length; i < _i; i++) {
+        const s = sets[i];
+        if (typeof s !== 'number') unionInto(ret, s as AtomSetElements);
+    }
+    if (size(ns as AtomSetImpl) > 0) {
+        if (typeof ns === 'number') unionIntoN(ret, ns as any);
+        else unionInto(ret, ns as AtomSetElements);
+    }
+    return ofObject(ret);
+}
+
+function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) {
+    let countN = 0, countE = 0;
+    for (let i = 0, _i = sets.length; i < _i; i++) {
+        if (typeof sets[i] === 'number') countN++;
+        else countE++;
+    }
+    eCount.count = countE;
+    if (!countN) return Empty;
+    if (countN === sets.length) return ofTuples(sets as ArrayLike<IntTuple>);
+    const packed = new Float64Array(countN);
+    let offset = 0;
+    for (let i = 0, _i = sets.length; i < _i; i++) {
+        const s = sets[i];
+        if (typeof s === 'number') packed[offset++] = s;
+    }
+    return ofTuples(packed as any);
+}
+
+function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) {
+    const keys = a.keys;
+    for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
+        const k = OrderedSet.getAt(keys, i);
+        const set = data[k];
+        if (set) data[k] = OrderedSet.union(set, a[k]);
+        else data[k] = a[k];
+    }
+}
+
+const _uIN = IntTuple.zero();
+function unionIntoN(data: { [key: number]: OrderedSet }, a: IntTuple) {
+    IntTuple.unpack(a, _uIN);
+    const set = data[_uIN.fst];
+    if (set) {
+        data[_uIN.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(_uIN.snd));
+    } else {
+        data[_uIN.fst] = OrderedSet.ofSingleton(_uIN.snd);
+    }
+}
\ No newline at end of file
diff --git a/src/mol-data/atom-set/builder.ts b/src/mol-data/atom-set/builder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..016dad1df797d4f22575463c803263b2566a50d6
--- /dev/null
+++ b/src/mol-data/atom-set/builder.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import AtomSet from '../atom-set'
+import OrderedSet from '../../mol-base/collections/ordered-set'
+import { sortArray } from '../../mol-base/collections/sort'
+
+class Builder {
+    private keys: number[] = [];
+    private units: number[][] = Object.create(null);
+    private currentUnit: number[] = [];
+
+    add(u: number, a: number) {
+        const unit = this.units[u];
+        if (!!unit) { unit[unit.length] = a; }
+        else {
+            this.units[u] = [a];
+            this.keys[this.keys.length] = u;
+        }
+    }
+
+    beginUnit() { this.currentUnit = []; }
+    addToUnit(a: number) { this.currentUnit[this.currentUnit.length] = a; }
+    commitUnit(u: number) { if (this.currentUnit.length === 0) return; this.keys[this.keys.length] = u; this.units[u] = this.currentUnit; }
+
+    getSet(): AtomSet {
+        const sets: { [key: number]: OrderedSet } = Object.create(null);
+        for (const k of this.keys) {
+            const unit = this.units[k];
+            const l = unit.length;
+            if (!this.sorted && l > 1) sortArray(unit);
+            if (l === 1) {
+                sets[k] = OrderedSet.ofSingleton(unit[0]);
+            } else {
+                const set = OrderedSet.ofSortedArray(unit);
+                const parentSet = AtomSet.getByKey(this.parent, k);
+                sets[k] = OrderedSet.areEqual(set, parentSet) ? parentSet : set;
+            }
+        }
+        return AtomSet.create(sets);
+    }
+
+    constructor(private parent: AtomSet, private sorted: boolean) { }
+}
+
+export default function createBuilder(parent: AtomSet, sorted: boolean) {
+    return new Builder(parent, sorted);
+}
\ No newline at end of file
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index 8920ab3e910905a6287a3df756bc3c892adfd607..45e76cd5ff8b9712327f01c8318b8365e9012306 100644
--- a/src/perf-tests/sets.ts
+++ b/src/perf-tests/sets.ts
@@ -200,4 +200,38 @@ export namespace Union {
     }
 }
 
-Union.runR();
\ No newline at end of file
+export namespace Build {
+    function createSorted() {
+        const b = AtomSet.SortedBuilder(AtomSet.Empty);
+        for (let i = 0; i < 100; i++) {
+            for (let j = 0; j < 100; j++) {
+                b.add(i, j);
+            }
+        }
+        return b.getSet();
+    }
+
+    function createByUnit() {
+        const b = AtomSet.SortedBuilder(AtomSet.Empty);
+        for (let i = 0; i < 100; i++) {
+            b.beginUnit();
+            for (let j = 0; j < 100; j++) {
+                b.addToUnit(j);
+            }
+            b.commitUnit(i);
+        }
+        return b.getSet();
+    }
+
+
+    export function run() {
+        const suite = new B.Suite();
+        suite
+            .add('create sorted', () => createSorted())
+            .add('create by unit', () => createByUnit())
+            .on('cycle', (e: any) => console.log(String(e.target)))
+            .run();
+    }
+}
+
+Build.run();
\ No newline at end of file