From 89a34d160b395cc1fddf300389d4e288cb93178c Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Fri, 27 Oct 2017 17:48:47 +0200
Subject: [PATCH] simplified IntTuple

---
 package-lock.json                            | Bin 154251 -> 154251 bytes
 package.json                                 |   8 +-
 src/mol-base/_spec/collections.spec.ts       |   8 +-
 src/mol-base/collections/int-tuple.ts        |  28 +----
 src/mol-base/collections/interval.ts         |  47 +++++++++
 src/mol-base/collections/ordered-set/base.ts |  71 +++++--------
 src/mol-data/_spec/atom-set.spec.ts          |  81 +++++++--------
 src/mol-data/atom-set.ts                     |   2 +-
 src/mol-data/atom-set/base.ts                | 104 +++++++++----------
 src/perf-tests/sets.ts                       |  47 ++++++++-
 10 files changed, 222 insertions(+), 174 deletions(-)
 create mode 100644 src/mol-base/collections/interval.ts

diff --git a/package-lock.json b/package-lock.json
index 47e6bce95bb6fce12ea990a40b6cdc0d9bde06e8..cfd951679b3a503d8fb86785c98a3e978ea34abc 100644
GIT binary patch
delta 619
zcmX}pO>5I&0KjpcY%m8p916pW4z@Y5LfX782f<`*lQvzG=B;T?HffV~ZQ3SllQsuI
z#M2HU&mk8<FM?jg9Yzm=A7O}YI|v@;#j6)Do<t9Rf<Hh0hsVo@$II_00G|EAo}Vni
zcVHDhet8SZtBbl2b0t}1sJtRY4o4JB(r4)kHb^=ae$d%roxEC<@*L6T30%bkyp_!i
zhd4=<$y~5IjTz1!(0nd0`J5ldRODvrU0zEs3SbRBSi1tNU<umDIy{>GD}O*%0S(y~
z8=4eXXo9L6(HLdqj!U6s&qVD5z82Y`NOpBEnYpo5bws1+?6bO|j*5&hZATTY-@;U$
zY0lz?AeAcn_)as-w;H(+?ZPIv`J}Tj?<@f26Fe$o>BG-pCH?&c+)lMS$cC;=lTxMN
z<)vbzdxME=nAKg^B3*hfqLh9uC(Y^%M)v%=vg`Q<W@J2yCj(#9eXouQdTlz&Hi95h
zdV$?eh#ESiTxIwdOQp}wz@_y36gcUREwD1@&%nkad>dR&znz17>Glb@o{sM$SK-g6
zH}sA-RQb@sNBapY3sug`h=ZadVnW;(v;(vevb-Krfu|X|<(pAXFPLUOYg<)L++!Jc
z(xUoGqu7%9Bs(>yGe=H*zYFD<OW)i??#^F*1+Po+^dpv{+sIP-XcM{ifA?4o`Vx)u
zmcSZnO!@Y>JjQxS)^hYv)CJn~7;H#l%|gbjWJAg^yR2$8h}K95YP4eTra!DrWEKn9
W9#3dJnPl7@6JC5@o3C#nJ@OAih}IAQ

delta 525
zcmX|;PjAw20EYAH7ID$c5KTB4=Q7=4CbWeXcC-9xX`#?RK!KCAjJCAUmeK+>@nSsf
zV9f8(#O$ygOgtGc3kN@f$+D>P;7w1)kKoD8!;bIid7iw7ryGZ-8y^DzPj2CF&Jyqg
zNJ8xG11KcdTSazO9~L9h@u|jQU?{xW>Z)>tnmkht7Nts%M$NuA@tLYP*v~h*IF~OM
zW^FNaA}ynLLczehnSdHK8QtrK`DuAXdJ+8jYX{ar0;<RsAmPIs*cJ8@lDr($(ROPp
zSwhxQ39icyDMzaIOR6>ydIhanZ|IRsxy#59>iY-e)M%SBM=2s>%N@2u=vt02B@`c-
zmf=RrOyCC@Y|d9~dq~G8=O6{2rZ!)iYb$dNi04<m@v|>rGyeM(JibomF<-9gS(2D%
zu{Iad=qL&_6!pVCgB7Z>-ioF!+7M;IN#_mR-W%hgK<J)K3sYhusWi<PN^db?m0GU1
z5bauyQdk{&pUL?31-Kc1{|2=9Jq_-xDi>gTJwAR8Zo{KjPvYG(a4-ISANjL7{sG>x
z|LxMdNFsi*gWSEgD`~1F=onU;<2I$Z^Ws=6b@n_*%5o;ll1tYY$22+fwcgBLFak}D
pWvRskywh%G(rCHjE#0<hrJJr*$+At8=MF?qygVee+Ioao#6Phqw*vqG

diff --git a/package.json b/package.json
index 6e12b2ee1..8b2461c3b 100644
--- a/package.json
+++ b/package.json
@@ -30,21 +30,21 @@
   "license": "MIT",
   "devDependencies": {
     "@types/benchmark": "^1.0.30",
-    "@types/jest": "^21.1.4",
-    "@types/node": "^8.0.46",
+    "@types/jest": "^21.1.5",
+    "@types/node": "^8.0.47",
     "benchmark": "^2.1.4",
     "download-cli": "^1.0.5",
     "jest": "^21.2.1",
     "rollup": "^0.50.0",
     "rollup-plugin-buble": "^0.16.0",
-    "rollup-plugin-commonjs": "^8.2.4",
+    "rollup-plugin-commonjs": "^8.2.5",
     "rollup-plugin-json": "^2.3.0",
     "rollup-plugin-node-resolve": "^3.0.0",
     "rollup-watch": "^4.3.1",
     "ts-jest": "^21.1.3",
     "tslint": "^5.8.0",
     "typescript": "^2.5.3",
-    "uglify-js": "^3.1.4",
+    "uglify-js": "^3.1.5",
     "util.promisify": "^1.0.0"
   },
   "dependencies": {}
diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts
index 71330f576..4b93ce45a 100644
--- a/src/mol-base/_spec/collections.spec.ts
+++ b/src/mol-base/_spec/collections.spec.ts
@@ -32,13 +32,11 @@ describe('basic iterators', () => {
 
 describe('int pair', () => {
     it('works', () => {
-        const p = IntTuple.zero();
         for (let i = 0; i < 10; i++) {
             for (let j = -10; j < 5; j++) {
-                const t = IntTuple.pack(i, j);
-                IntTuple.unpack(t, p);
-                expect(p.fst).toBe(i);
-                expect(p.snd).toBe(j);
+                const t = IntTuple.create(i, j);
+                expect(IntTuple.fst(t)).toBe(i);
+                expect(IntTuple.snd(t)).toBe(j);
             }
         }
     })
diff --git a/src/mol-base/collections/int-tuple.ts b/src/mol-base/collections/int-tuple.ts
index 64e97ca82..da27ab08f 100644
--- a/src/mol-base/collections/int-tuple.ts
+++ b/src/mol-base/collections/int-tuple.ts
@@ -13,7 +13,7 @@ import { hash2 } from './hash-functions'
 interface IntTuple { '@type': 'int-tuple' }
 
 namespace IntTuple {
-    export interface Unpacked { fst: number, snd: number }
+    export const Zero: IntTuple = 0 as any;
 
     const { _int32, _float64, _int32_1, _float64_1 } = (function() {
         const data = new ArrayBuffer(8);
@@ -26,36 +26,16 @@ namespace IntTuple {
         };
     }());
 
-    export function is(x: any): x is Unpacked {
-        return !!x && typeof x.fst === 'number' && typeof x.snd === 'number';
+    export function is(x: any): x is IntTuple {
+        return typeof x === 'number';
     }
 
-    export function create(fst: number, snd: number) { return { fst, snd }; }
-    export function zero(): Unpacked { return { fst: 0, snd: 0 }; }
-
-    export function pack(fst: number, snd: number): IntTuple {
+    export function create(fst: number, snd: number): IntTuple {
         _int32[0] = fst;
         _int32[1] = snd;
         return _float64[0] as any;
     }
 
-    export function pack1(t: Unpacked): IntTuple {
-        _int32[0] = t.fst;
-        _int32[1] = t.snd;
-        return _float64[0] as any;
-    }
-
-    export function unpack(t: IntTuple, target: Unpacked): Unpacked {
-        _float64[0] = t as any;
-        target.fst = _int32[0];
-        target.snd = _int32[1];
-        return target;
-    }
-
-    export function unpack1(packed: IntTuple): Unpacked {
-        return unpack(packed, zero());
-    }
-
     export function fst(t: IntTuple): number {
         _float64[0] = t as any;
         return _int32[0];
diff --git a/src/mol-base/collections/interval.ts b/src/mol-base/collections/interval.ts
new file mode 100644
index 000000000..722d48d39
--- /dev/null
+++ b/src/mol-base/collections/interval.ts
@@ -0,0 +1,47 @@
+/**
+ * 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'
+
+/** Closed/Open inteval [a, b) (iterate as a <= i < b) */
+namespace Interval {
+    export const Empty: Interval = Impl.Empty as any;
+
+    // export const create: (min: number, max: number) => Interval = Impl.create as any;
+
+    // export const indexOf: (set: Interval, x: number) => number = Impl.indexOf as any;
+    // export const getAt: (set: Interval, i: number) => number = Impl.getAt as any;
+
+    // export const start: (set: Interval) => number = Impl.start as any;
+    // export const end: (set: Interval) => number = Impl.end as any;
+
+    // export const min: (set: Interval) => number = Impl.min as any;
+    // export const max: (set: Interval) => number = Impl.max as any;
+    // export const size: (set: Interval) => number = Impl.size as any;
+    // export const hashCode: (set: Interval) => number = Impl.hashCode as any;
+
+    // export const areEqual: (a: Interval, b: Interval) => boolean = Impl.areEqual as any;
+    // export const areIntersecting: (a: Interval, b: Interval) => boolean = Impl.areIntersecting as any;
+    // export const isSubset: (a: Interval, b: Interval) => boolean = Impl.isSubset as any;
+
+}
+
+interface Interval { '@type': 'int-interval' }
+
+export default Interval
+
+namespace Impl {export const Empty = IntTuple.Zero;
+
+    export function create(min: number, max: number) { return max < min ? Empty : IntTuple.create(min, max + 1); }
+
+    export const start = IntTuple.fst
+    export const end = IntTuple.snd
+    export const min = IntTuple.fst
+    export function max(i: IntTuple) { return IntTuple.snd(i) + 1; }
+    export function size(i: IntTuple) { return IntTuple.snd(i) - IntTuple.fst(i); }
+
+    export function has(int: IntTuple, v: number) { return IntTuple.fst(int) <= v && v < IntTuple.snd(int); }
+}
\ 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
index 1eddd1ad5..b37bc56e1 100644
--- a/src/mol-base/collections/ordered-set/base.ts
+++ b/src/mol-base/collections/ordered-set/base.ts
@@ -11,9 +11,9 @@ 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; }
+export const Empty: OrderedSetImpl = IntTuple.create(0, -1) as any;
+export function ofSingleton(value: number): OrderedSetImpl { return IntTuple.create(value, value) as any; }
+export function ofRange(min: number, max: number): OrderedSetImpl { return max < min ? Empty : IntTuple.create(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;
@@ -120,10 +120,9 @@ 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 sizeR(set: Range) { return maxR(set) - minR(set) + 1; }
+function hasR(set: Range, x: number) { return x >= minR(set) && x <= maxR(set); }
+function indexOfR(set: Range, x: number) { const m = minR(set); return x >= m && x <= maxR(set) ? x - m : -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; }
@@ -169,12 +168,12 @@ function binarySearchPredIndexRange(xs: SortedArray, value: number, start: numbe
     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 min = minR(r);
+    if (value < min) return 0;
+    const max = maxR(r);
+    if (value > max) return max - min + 1;
+    return value - min;
 }
 
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
@@ -262,15 +261,13 @@ function unionRR(a: Range, b: Range) {
     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 min = minR(b), max = maxR(b);
     const { start, end } = getStartEnd(a, min, max);
 
     const indices = new Int32Array(start + (a.length - end) + bSize);
@@ -332,22 +329,16 @@ function unionAA(a: SortedArray, b: SortedArray) {
     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));
+    return ofRange(Math.max(minR(a), minR(b)), Math.min(maxR(a), maxR(b)));
 }
 
-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 { start, end } = getStartEnd(a, minR(r), maxR(r));
     const resultSize = end - start;
     if (!resultSize) return Empty;
 
@@ -394,41 +385,37 @@ function intersectAA(a: SortedArray, b: SortedArray) {
     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);
+    const minA = minR(a), maxA = maxR(a);
+    const minB = minR(b), maxB = maxR(b);
 
-    if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a;
+    if (maxA < minA || maxB < minB) 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 l = minB - minA, r = maxA - maxB;
+        if (l <= 0) return ofRange(maxB + 1, maxB + r);
+        if (r <= 0) return ofRange(minA, minA + 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;
+        for (let i = 0; i < l; i++) ret[offset++] = minA + i;
+        for (let i = 1; i <= r; i++) ret[offset++] = maxB + 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);
+    if (minA < minB) return ofRange(minA, minB - 1);
+    return ofRange(maxB + 1, maxA);
 }
 
-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 = minR(b), max = maxR(b);
+    if (max < min) 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
@@ -443,14 +430,12 @@ function subtractAR(a: SortedArray, b: Range) {
     return ofSortedArray(ret);
 }
 
-const _sAR1 = IntTuple.zero();
 function subtractRA(a: Range, b: SortedArray) {
-    IntTuple.unpack(a, _sAR1);
+    const min = minR(a), max = maxR(a);
 
     // is empty?
-    if (_sAR1.snd < _sAR1.fst) return a;
+    if (max < min) 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;
diff --git a/src/mol-data/_spec/atom-set.spec.ts b/src/mol-data/_spec/atom-set.spec.ts
index 603909f1e..b34cbc2ca 100644
--- a/src/mol-data/_spec/atom-set.spec.ts
+++ b/src/mol-data/_spec/atom-set.spec.ts
@@ -10,26 +10,25 @@ import AtomSet from '../atom-set'
 
 describe('atom set', () => {
     const p = (i: number, j: number) => IntTuple.create(i, j);
-    const r = (i: number, j: number) => IntTuple.pack(i, j);
 
-    function setToPairs(set: AtomSet): ArrayLike<IntTuple.Unpacked> {
-        const ret = [];
+    function setToPairs(set: AtomSet): ArrayLike<IntTuple> {
+        const ret: IntTuple[] = [];
         const it = AtomSet.atoms(set);
-        for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = IntTuple.create(v.fst, v.snd);
+        for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = v;
         return ret;
     }
 
     it('singleton pair', () => {
         const set = AtomSet.create(p(10, 11));
         expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(AtomSet.hasAtom(set, r(10, 11))).toBe(true);
-        expect(AtomSet.hasAtom(set, r(11, 11))).toBe(false);
-        expect(AtomSet.getAtomAt(set, 0)).toBe(r(10, 11));
+        expect(AtomSet.hasAtom(set, p(10, 11))).toBe(true);
+        expect(AtomSet.hasAtom(set, p(11, 11))).toBe(false);
+        expect(AtomSet.getAtomAt(set, 0)).toBe(p(10, 11));
         expect(AtomSet.atomCount(set)).toBe(1);
     });
 
     it('singleton number', () => {
-        const set = AtomSet.create(r(10, 11));
+        const set = AtomSet.create(p(10, 11));
         expect(setToPairs(set)).toEqual([p(10, 11)]);
     });
 
@@ -41,11 +40,11 @@ describe('atom set', () => {
         const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)];
         expect(AtomSet.atomCount(set)).toBe(ret.length);
         expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
-        expect(AtomSet.hasAtom(set, r(10, 11))).toBe(false);
-        expect(AtomSet.hasAtom(set, r(3, 0))).toBe(true);
-        expect(AtomSet.hasAtom(set, r(1, 7))).toBe(true);
+        expect(AtomSet.hasAtom(set, p(10, 11))).toBe(false);
+        expect(AtomSet.hasAtom(set, p(3, 0))).toBe(true);
+        expect(AtomSet.hasAtom(set, p(1, 7))).toBe(true);
         for (let i = 0; i < AtomSet.atomCount(set); i++) {
-            expect(AtomSet.getAtomAt(set, i)).toBe(IntTuple.pack1(ret[i]));
+            expect(IntTuple.areEqual(AtomSet.getAtomAt(set, i), ret[i])).toBe(true);
         }
     });
 
@@ -55,7 +54,7 @@ describe('atom set', () => {
         for (let i = 1; i < 10; i++) {
             const set = [];
             for (let j = 1; j < 7; j++) {
-                control[control.length] = r(i * i, j * j + 1);
+                control[control.length] = p(i * i, j * j + 1);
                 set[set.length] = j * j + 1;
             }
             sets[i * i] = OrderedSet.ofSortedArray(set);
@@ -71,17 +70,17 @@ describe('atom set', () => {
     });
 
     it('packed pairs', () => {
-        const set = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
+        const set = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
         expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
     });
 
     it('equality', () => {
-        const a = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const b = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const c = AtomSet.create([r(1, 3), r(0, 4), r(0, 6), r(0, 2)]);
-        const d = AtomSet.create([r(1, 3)]);
-        const e = AtomSet.create([r(1, 3)]);
-        const f = AtomSet.create([r(3, 3)]);
+        const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const b = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const c = AtomSet.create([p(1, 3), p(0, 4), p(0, 6), p(0, 2)]);
+        const d = AtomSet.create([p(1, 3)]);
+        const e = AtomSet.create([p(1, 3)]);
+        const f = AtomSet.create([p(3, 3)]);
 
         expect(AtomSet.areEqual(a, a)).toBe(true);
         expect(AtomSet.areEqual(a, b)).toBe(true);
@@ -93,13 +92,13 @@ describe('atom set', () => {
     });
 
     it('are intersecting', () => {
-        const a = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const b = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const c = AtomSet.create([r(1, 3), r(0, 4), r(0, 6), r(0, 2)]);
-        const d = AtomSet.create([r(1, 3)]);
-        const e = AtomSet.create([r(1, 3)]);
-        const f = AtomSet.create([r(3, 3)]);
-        const g = AtomSet.create([r(10, 3), r(8, 1), r(7, 6), r(3, 2)]);
+        const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const b = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const c = AtomSet.create([p(1, 3), p(0, 4), p(0, 6), p(0, 2)]);
+        const d = AtomSet.create([p(1, 3)]);
+        const e = AtomSet.create([p(1, 3)]);
+        const f = AtomSet.create([p(3, 3)]);
+        const g = AtomSet.create([p(10, 3), p(8, 1), p(7, 6), p(3, 2)]);
 
         expect(AtomSet.areIntersecting(a, a)).toBe(true);
         expect(AtomSet.areIntersecting(a, b)).toBe(true);
@@ -112,10 +111,10 @@ describe('atom set', () => {
     });
 
     it('intersection', () => {
-        const a = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const b = AtomSet.create([r(10, 3), r(0, 1), r(0, 6), r(4, 2)]);
-        const c = AtomSet.create([r(1, 3)]);
-        const d = AtomSet.create([r(2, 3)]);
+        const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const b = AtomSet.create([p(10, 3), p(0, 1), p(0, 6), p(4, 2)]);
+        const c = AtomSet.create([p(1, 3)]);
+        const d = AtomSet.create([p(2, 3)]);
         expect(AtomSet.intersect(a, a)).toBe(a);
         expect(setToPairs(AtomSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]);
         expect(setToPairs(AtomSet.intersect(a, c))).toEqual([p(1, 3)]);
@@ -123,11 +122,11 @@ describe('atom set', () => {
     });
 
     it('subtract', () => {
-        const a = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const a1 = AtomSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
-        const b = AtomSet.create([r(10, 3), r(0, 1), r(0, 6), r(4, 2)]);
-        const c = AtomSet.create([r(1, 3)]);
-        const d = AtomSet.create([r(2, 3)]);
+        const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const a1 = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const b = AtomSet.create([p(10, 3), p(0, 1), p(0, 6), p(4, 2)]);
+        const c = AtomSet.create([p(1, 3)]);
+        const d = AtomSet.create([p(2, 3)]);
         expect(setToPairs(AtomSet.subtract(a, a))).toEqual([]);
         expect(setToPairs(AtomSet.subtract(a, a1))).toEqual([]);
         expect(setToPairs(AtomSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]);
@@ -138,11 +137,11 @@ describe('atom set', () => {
     });
 
     it('union', () => {
-        const a = AtomSet.create([r(1, 3), r(0, 1)]);
-        const a1 = AtomSet.create([r(1, 3), r(0, 1)]);
-        const b = AtomSet.create([r(10, 3), r(0, 1)]);
-        const c = AtomSet.create([r(1, 3)]);
-        const d = AtomSet.create([r(2, 3)]);
+        const a = AtomSet.create([p(1, 3), p(0, 1)]);
+        const a1 = AtomSet.create([p(1, 3), p(0, 1)]);
+        const b = AtomSet.create([p(10, 3), p(0, 1)]);
+        const c = AtomSet.create([p(1, 3)]);
+        const d = AtomSet.create([p(2, 3)]);
         expect(AtomSet.unionMany([a])).toBe(a);
         expect(AtomSet.union(a, a)).toBe(a);
         expect(setToPairs(AtomSet.union(a, a))).toEqual([p(0, 1), p(1, 3)]);
diff --git a/src/mol-data/atom-set.ts b/src/mol-data/atom-set.ts
index f28399dd6..abf072be7 100644
--- a/src/mol-data/atom-set.ts
+++ b/src/mol-data/atom-set.ts
@@ -27,7 +27,7 @@ namespace AtomSet {
     export const hasAtom: (set: AtomSet, x: IntTuple) => boolean = Base.hasTuple as any;
     export const indexOfAtom: (set: AtomSet, x: IntTuple) => number = Base.indexOf as any;
     export const getAtomAt: (set: AtomSet, i: number) => IntTuple = Base.getAt as any;
-    export const atoms: (set: AtomSet) => Iterator<IntTuple.Unpacked> = Base.values as any;
+    export const atoms: (set: AtomSet) => Iterator<IntTuple> = Base.values as any;
 
     export const atomCount: (set: AtomSet) => number = Base.size as any;
 
diff --git a/src/mol-data/atom-set/base.ts b/src/mol-data/atom-set/base.ts
index 2147fc755..ea1aff390 100644
--- a/src/mol-data/atom-set/base.ts
+++ b/src/mol-data/atom-set/base.ts
@@ -17,9 +17,8 @@ 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);
+export function create(data: IntTuple | ArrayLike<IntTuple> | { [id: number]: OrderedSet }): AtomSetImpl {
+    if (typeof data === 'number' || IntTuple.is(data)) return data;
     if (isArrayLike(data)) return ofTuples(data);
     return ofObject(data as { [id: number]: OrderedSet });
 }
@@ -50,19 +49,17 @@ export function getKey(set: AtomSetImpl, index: number): number {
 
 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 unit = IntTuple.fst(t);
+    return OrderedSet.has((set as AtomSetElements).keys, unit)
+        ? OrderedSet.has((set as AtomSetElements)[unit], IntTuple.snd(t)) : 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 IntTuple.fst(set) === key ? OrderedSet.ofSingleton(IntTuple.snd(set)) : 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;
@@ -136,9 +133,8 @@ export function unionMany(sets: ArrayLike<AtomSetImpl>) {
     return findUnion(sets);
 }
 
-class ElementsIterator implements Iterator<IntTuple.Unpacked> {
-    private pair = IntTuple.zero();
-
+class ElementsIterator implements Iterator<IntTuple> {
+    private unit: number = 0;
     private keyCount: number;
     private setIndex = -1;
     private currentIndex = 0;
@@ -150,14 +146,13 @@ class ElementsIterator implements Iterator<IntTuple.Unpacked> {
     next() { const value = this.move(); return { value, done: this.done } }
 
     move() {
-        if (this.done) return this.pair;
+        if (this.done) return IntTuple.Zero;
 
         if (this.currentIndex >= this.currentSize) {
-            if (!this.advance()) return this.pair;
+            if (!this.advance()) return IntTuple.Zero;
         }
 
-        this.pair.snd = OrderedSet.getAt(this.currentSet, this.currentIndex++);
-        return this.pair;
+        return IntTuple.create(this.unit, OrderedSet.getAt(this.currentSet, this.currentIndex++));
     }
 
     private advance() {
@@ -165,9 +160,8 @@ class ElementsIterator implements Iterator<IntTuple.Unpacked> {
             this.done = true;
             return false;
         }
-        const unit = OrderedSet.getAt(this.elements.keys, this.setIndex);
-        this.pair.fst = unit;
-        this.currentSet = this.elements[unit];
+        this.unit = OrderedSet.getAt(this.elements.keys, this.setIndex);
+        this.currentSet = this.elements[this.unit];
         this.currentIndex = 0;
         this.currentSize = OrderedSet.size(this.currentSet);
         return true;
@@ -180,8 +174,8 @@ class ElementsIterator implements Iterator<IntTuple.Unpacked> {
     }
 }
 
-export function values(set: AtomSetImpl): Iterator<IntTuple.Unpacked> {
-    if (typeof set === 'number') return Iterator.Value(IntTuple.unpack1(set));
+export function values(set: AtomSetImpl): Iterator<IntTuple> {
+    if (typeof set === 'number') return Iterator.Value(set as IntTuple);
     return new ElementsIterator(set as AtomSetElements);
 }
 
@@ -198,7 +192,7 @@ function ofObject(data: { [id: number]: OrderedSet }) {
     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));
+        if (OrderedSet.size(set) === 1) return IntTuple.create(keys[0], OrderedSet.getAt(set, 0));
     }
     return ofObject1(keys, data);
 }
@@ -207,7 +201,7 @@ 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));
+        if (OrderedSet.size(set) === 1) return IntTuple.create(k, OrderedSet.getAt(set, 0));
     }
     sortArray(keys);
     return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data);
@@ -217,7 +211,7 @@ 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));
+        if (OrderedSet.size(set) === 1) return IntTuple.create(k, OrderedSet.getAt(set, 0));
     }
     return _createObjectOrdered(keys, data);
 }
@@ -264,12 +258,12 @@ function normalizeArray(xs: number[]) {
 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 x = xs[i];
+        const u = IntTuple.fst(x), v = IntTuple.snd(x);
+        const set = sets[u];
+        if (set) set[set.length] = v;
+        else sets[u] = [v];
     }
     const ret: { [key: number]: OrderedSet } = Object.create(null);
     const keys = [];
@@ -302,16 +296,15 @@ function getAtE(set: AtomSetElements, i: number): IntTuple {
     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);
+    return IntTuple.create(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);
+    const u = IntTuple.fst(t);
+    const setIdx = OrderedSet.indexOf(keys, u);
     if (setIdx < 0) return -1;
-    const o = OrderedSet.indexOf(set[_iOE.fst], _iOE.snd);
+    const o = OrderedSet.indexOf(set[u], IntTuple.snd(t));
     if (o < 0) return -1;
     return set.offsets[setIdx] + o;
 }
@@ -343,10 +336,9 @@ function areEqualEE(a: AtomSetElements, b: AtomSetElements) {
     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);
+    const u = IntTuple.fst(a);
+    return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], IntTuple.snd(a));
 }
 
 function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
@@ -361,10 +353,9 @@ function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
     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;
+    const u = IntTuple.fst(a);
+    return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], IntTuple.snd(a)) ? a : Empty;
 }
 
 function intersectEE(a: AtomSetElements, b: AtomSetElements) {
@@ -388,22 +379,28 @@ function intersectEE(a: AtomSetElements, b: AtomSetElements) {
     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 u = IntTuple.fst(a);
+    return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], IntTuple.snd(a)) ? 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];
+    const u = IntTuple.fst(b), v = IntTuple.snd(b);
+    if (!OrderedSet.has(aKeys, u) || !OrderedSet.has(a[u], v)) return a;
+    const set = a[u];
     if (OrderedSet.size(set) === 1) {
-        return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(_sEN.fst)), a);
+        // remove the entire unit.
+        return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(u)), a);
     } else {
-        return ofObjectOrdered(OrderedSet.subtract(set, OrderedSet.ofSingleton(_sEN.snd)), a);
+        const ret: { [key: number]: OrderedSet } = Object.create(null);
+        for (let i = 0, _i = OrderedSet.size(a.keys); i < _i; i++) {
+            const k = OrderedSet.getAt(a.keys, i);
+            if (k === u) {
+                ret[k] = OrderedSet.subtract(set, OrderedSet.ofSingleton(v));
+            } else ret[k] = a[k];
+        }
+        return ofObjectOrdered(a.keys, ret);
     }
 }
 
@@ -489,13 +486,12 @@ function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) {
     }
 }
 
-const _uIN = IntTuple.zero();
 function unionIntoN(data: { [key: number]: OrderedSet }, a: IntTuple) {
-    IntTuple.unpack(a, _uIN);
-    const set = data[_uIN.fst];
+    const u = IntTuple.fst(a);
+    const set = data[u];
     if (set) {
-        data[_uIN.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(_uIN.snd));
+        data[u] = OrderedSet.union(set, OrderedSet.ofSingleton(IntTuple.snd(a)));
     } else {
-        data[_uIN.fst] = OrderedSet.ofSingleton(_uIN.snd);
+        data[u] = OrderedSet.ofSingleton(IntTuple.snd(a));
     }
 }
\ No newline at end of file
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index 1fba7dfe1..4930c3e84 100644
--- a/src/perf-tests/sets.ts
+++ b/src/perf-tests/sets.ts
@@ -27,7 +27,7 @@ export namespace Iteration {
     export function iterators() {
         let s = 0;
         const it = AtomSet.atoms(ms);
-        for (let v = it.move(); !it.done; v = it.move()) s += v.snd;
+        for (let v = it.move(); !it.done; v = it.move()) s += IntTuple.snd(v);
         return s;
     }
 
@@ -230,6 +230,43 @@ export namespace Build {
     }
 }
 
+export namespace Tuples {
+    function createData(n: number) {
+        const ret: IntTuple[] = new Float64Array(n) as any;
+        for (let i = 0; i < n; i++) {
+            ret[i] = IntTuple.create(i, i * i + 1);
+        }
+        return ret;
+    }
+
+    function sum1(data: ArrayLike<IntTuple>) {
+        let s = 0;
+        for (let i = 0, _i = data.length; i < _i; i++) {
+            s += IntTuple.fst(data[i]) + IntTuple.snd(data[i]);
+        }
+        return s;
+    }
+
+    function sum2(data: ArrayLike<IntTuple>) {
+        let s = 0;
+        for (let i = 0, _i = data.length; i < _i; i++) {
+            const t = data[i];
+            s += IntTuple.fst(t) + IntTuple.snd(t);
+        }
+        return s;
+    }
+
+    export function run() {
+        const suite = new B.Suite();
+        const data = createData(10000);
+        suite
+            .add('sum fst/snd', () => sum1(data))
+            .add('sum fst/snd 1', () => sum2(data))
+            .on('cycle', (e: any) => console.log(String(e.target)))
+            .run();
+    }
+}
+
 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]);
@@ -242,4 +279,10 @@ export function testSegments() {
     }
 }
 
-Union.run();
\ No newline at end of file
+Tuples.run();
+
+// interface AA { kind: 'a' }
+// //interface BB { kind: 'b' }
+// interface AB { kind: 'a' | 'b' }
+// declare const a: AA;
+// export const ab: AB = a;
-- 
GitLab