From ecbe3d2ef0eaf247e86f635e97ed9948b26d9993 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Fri, 10 Nov 2017 12:58:42 +0100
Subject: [PATCH] Refactoring AtomSet to use IntMap (step 1 of ?)

---
 src/mol-data/int/map.ts                       |  5 +-
 .../structure/structure/atom/set/impl.ts      | 76 ++++++++++---------
 src/perf-tests/structure.ts                   | 10 +--
 3 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/src/mol-data/int/map.ts b/src/mol-data/int/map.ts
index 1ef86a532..9c2c2aca9 100644
--- a/src/mol-data/int/map.ts
+++ b/src/mol-data/int/map.ts
@@ -8,13 +8,16 @@ import { iterableToArray } from '../util'
 
 /** Immutable by convention IntMap */
 interface IntMap<T> {
+    has(key: number): boolean,
     keys(): IterableIterator<number>,
     values(): IterableIterator<T>,
-    get(key: number): T,
+    get(key: number): T | undefined,
     readonly size: number
 }
 
 namespace IntMap {
+    export const Empty: IntMap<any> = new Map<number, any>();
+
     export interface Mutable<T> extends IntMap<T> {
         set(key: number, value: T): void;
     }
diff --git a/src/mol-model/structure/structure/atom/set/impl.ts b/src/mol-model/structure/structure/atom/set/impl.ts
index f60658f1f..0ada96dc1 100644
--- a/src/mol-model/structure/structure/atom/set/impl.ts
+++ b/src/mol-model/structure/structure/atom/set/impl.ts
@@ -4,17 +4,17 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { SortedArray, Interval, Iterator, OrderedSet } from 'mol-data/int'
+import { SortedArray, Interval, Iterator, OrderedSet, IntMap } from 'mol-data/int'
 import { sortArray } from 'mol-data/util/sort'
 import { hash1 } from 'mol-data/util/hash-functions'
 import Atom from '../../atom'
 
 /** Long and painful implementation starts here */
 
-export interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: SortedArray }
+export interface AtomSetElements { sets: IntMap<OrderedSet>, offsets: number[], hashCode: number, keys: SortedArray }
 export type AtomSetImpl = Atom | AtomSetElements
 
-export const Empty: AtomSetImpl = { offsets: [0], hashCode: 0, keys: SortedArray.Empty };
+export const Empty: AtomSetImpl = { sets: IntMap.Empty, offsets: [0], hashCode: 0, keys: SortedArray.Empty };
 
 export function create(data: Atom | ArrayLike<Atom> | { [id: number]: OrderedSet }): AtomSetImpl {
     if (typeof data === 'number' || Atom.is(data)) return data;
@@ -38,7 +38,7 @@ export function keyCount(set: AtomSetImpl): number {
 
 export function hasKey(set: AtomSetImpl, key: number): boolean {
     if (typeof set === 'number') return Atom.unit(set) === key;
-    return !!(set as AtomSetElements)[key]
+    return !!(set as AtomSetElements).sets.has(key);
 }
 
 export function getKey(set: AtomSetImpl, index: number): number {
@@ -48,7 +48,7 @@ export function getKey(set: AtomSetImpl, index: number): number {
 
 export function hasAtom(set: AtomSetImpl, t: Atom): boolean {
     if (typeof set === 'number') return Atom.areEqual(t, set);
-    const os = (set as AtomSetElements)[Atom.unit(t)];
+    const os = (set as AtomSetElements).sets.get(Atom.unit(t));
     return !!os && OrderedSet.has(os, Atom.index(t));
 }
 
@@ -56,13 +56,13 @@ export function getByKey(set: AtomSetImpl, key: number): OrderedSet {
     if (typeof set === 'number') {
         return Atom.unit(set) === key ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty;
     }
-    return (set as AtomSetElements)[key] || OrderedSet.Empty;
+    return (set as AtomSetElements).sets.get(key) || OrderedSet.Empty;
 }
 
 export function getByIndex(set: AtomSetImpl, index: number): OrderedSet {
     if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty;
     const key = (set as AtomSetElements).keys[index];
-    return (set as AtomSetElements)[key] || OrderedSet.Empty;
+    return (set as AtomSetElements).sets.get(key) || OrderedSet.Empty;
 }
 
 export function getAt(set: AtomSetImpl, i: number): Atom {
@@ -154,7 +154,7 @@ class ElementsIterator implements Iterator<Atom> {
             return false;
         }
         this.unit = this.elements.keys[this.setIndex];
-        this.currentSet = this.elements[this.unit];
+        this.currentSet = this.elements.sets.get(this.unit)!;
         this.currentIndex = 0;
         this.currentSize = OrderedSet.size(this.currentSet);
         return true;
@@ -211,21 +211,18 @@ function ofObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet })
     return _createObjectOrdered(keys, data);
 }
 
-function _createObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) {
-    const ret: AtomSetElements = Object.create(null);
-    ret.keys = keys;
+function _createObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }): AtomSetElements {
+    const sets = IntMap.Mutable<OrderedSet>();
     const offsets = [0];
     let runningSize = 0;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
         const set = data[k];
-        ret[k] = set;
+        sets.set(k, set);
         runningSize += OrderedSet.size(set);
         offsets[offsets.length] = runningSize;
     }
-    ret.offsets = offsets;
-    ret.hashCode = -1;
-    return ret;
+    return { sets, keys, offsets, hashCode: - 1 };
 }
 
 function getUniqueElements(xs: number[]) {
@@ -291,7 +288,7 @@ function getAtE(set: AtomSetElements, i: number): Atom {
     const o = getOffsetIndex(offsets, i);
     if (o >= offsets.length - 1) return 0 as any;
     const k = keys[o];
-    const e = OrderedSet.getAt(set[k], i - offsets[o]);
+    const e = OrderedSet.getAt(set.sets.get(k)!, i - offsets[o]);
     return Atom.create(k, e);
 }
 
@@ -300,18 +297,18 @@ function indexOfE(set: AtomSetElements, t: Atom) {
     const u = Atom.unit(t);
     const setIdx = SortedArray.indexOf(keys, u);
     if (setIdx < 0) return -1;
-    const o = OrderedSet.indexOf(set[u], Atom.index(t));
+    const o = OrderedSet.indexOf(set.sets.get(u)!, Atom.index(t));
     if (o < 0) return -1;
     return set.offsets[setIdx] + o;
 }
 
 function computeHash(set: AtomSetElements) {
-    const { keys } = set;
+    const { keys, sets } = set;
     let hash = 23;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
         hash = (31 * hash + k) | 0;
-        hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0;
+        hash = (31 * hash + OrderedSet.hashCode(sets.get(k)!)) | 0;
     }
     hash = (31 * hash + size(set)) | 0;
     hash = hash1(hash);
@@ -325,16 +322,18 @@ function areEqualEE(a: AtomSetElements, b: AtomSetElements) {
 
     const keys = a.keys;
     if (!SortedArray.areEqual(keys, b.keys)) return false;
+    const { sets: aSets } = a;
+    const { sets: bSets } = b;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        if (!OrderedSet.areEqual(a[k], b[k])) return false;
+        if (!OrderedSet.areEqual(aSets.get(k)!, bSets.get(k)!)) return false;
     }
     return true;
 }
 
 function areIntersectingNE(a: Atom, b: AtomSetElements) {
     const u = Atom.unit(a);
-    return SortedArray.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a));
+    return b.sets.has(u) && OrderedSet.has(b.sets.get(u)!, Atom.index(a));
 }
 
 function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
@@ -343,9 +342,11 @@ function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
     if (!SortedArray.areIntersecting(a.keys, b.keys)) return false;
     const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
     const start = Interval.start(r), end = Interval.end(r);
+    const { sets: aSets } = a;
+    const { sets: bSets } = b;
     for (let i = start; i < end; i++) {
         const k = keysA[i];
-        const ak = a[k], bk = b[k];
+        const ak = aSets.get(k), bk = bSets.get(k);
         if (!!ak && !!bk && OrderedSet.areIntersecting(ak, bk)) return true;
     }
     return false;
@@ -353,7 +354,7 @@ function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
 
 function intersectNE(a: Atom, b: AtomSetElements) {
     const u = Atom.unit(a);
-    return !!b[u] && OrderedSet.has(b[u], Atom.index(a)) ? a : Empty;
+    return b.sets.has(u) && OrderedSet.has(b.sets.get(u)!, Atom.index(a)) ? a : Empty;
 }
 
 function intersectEE(a: AtomSetElements, b: AtomSetElements) {
@@ -365,11 +366,13 @@ function intersectEE(a: AtomSetElements, b: AtomSetElements) {
     const start = Interval.start(r), end = Interval.end(r);
 
     const keys = [], ret = Object.create(null);
+    const { sets: aSets } = a;
+    const { sets: bSets } = b;
     for (let i = start; i < end; i++) {
         const k = keysA[i];
-        const bk = b[k];
+        const bk = bSets.get(k);
         if (!bk) continue;
-        const intersection = OrderedSet.intersect(a[k], b[k]);
+        const intersection = OrderedSet.intersect(aSets.get(k)!, bk);
         if (OrderedSet.size(intersection) > 0) {
             keys[keys.length] = k;
             ret[k] = intersection;
@@ -386,17 +389,19 @@ function subtractEN(a: AtomSetElements, b: Atom): AtomSetImpl {
     if (!hasAtom(a, b)) return a;
 
     const u = Atom.unit(b), v = Atom.index(b);
-    const set = a[u];
+    const { sets: aSets } = a;
+    const set = aSets.get(u)!;
     if (OrderedSet.size(set) === 1) {
         // remove the entire unit.
-        return ofObjectOrdered(SortedArray.subtract(a.keys, SortedArray.ofSingleton(u)), a);
+        throw 'nyi'
+        //return ofObjectOrdered(SortedArray.subtract(a.keys, SortedArray.ofSingleton(u)), a);
     } else {
         const ret: { [key: number]: OrderedSet } = Object.create(null);
         for (let i = 0, _i = a.keys.length; i < _i; i++) {
             const k = a.keys[i];
             if (k === u) {
                 ret[k] = OrderedSet.subtract(set, OrderedSet.ofSingleton(v));
-            } else ret[k] = a[k];
+            } else ret[k] = aSets.get(k)!;
         }
         return ofObjectOrdered(a.keys, ret);
     }
@@ -411,14 +416,16 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) {
     const start = Interval.start(r), end = Interval.end(r);
 
     const keys = [], ret = Object.create(null);
+    const { sets: aSets } = a;
+    const { sets: bSets } = b;
     for (let i = 0; i < start; i++) {
         const k = keysA[i];
         keys[keys.length] = k;
-        ret[k] = a[k];
+        ret[k] = aSets.get(k);
     }
     for (let i = start; i < end; i++) {
         const k = keysA[i];
-        const ak = a[k], bk = b[k];
+        const ak = aSets.get(k)!, bk = bSets.get(k);
         if (!!bk) {
             const subtraction = OrderedSet.subtract(ak, bk);
             if (OrderedSet.size(subtraction) > 0) {
@@ -427,13 +434,13 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) {
             }
         } else {
             keys[keys.length] = k;
-            ret[k] = a[k];
+            ret[k] = ak;
         }
     }
     for (let i = end, _i = keysA.length; i < _i; i++) {
         const k = keysA[i];
         keys[keys.length] = k;
-        ret[k] = a[k];
+        ret[k] = aSets.get(k);
     }
     return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret);
 }
@@ -478,11 +485,12 @@ function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) {
 
 function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) {
     const keys = a.keys;
+    const { sets: aSets } = a;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
         const set = data[k];
-        if (set) data[k] = OrderedSet.union(set, a[k]);
-        else data[k] = a[k];
+        if (set) data[k] = OrderedSet.union(set, aSets.get(k)!);
+        else data[k] = aSets.get(k)!;
     }
 }
 
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 9898edf50..0de52636a 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -252,17 +252,17 @@ export namespace PropertyAccess {
 
     export async function run() {
         //const { structures, models, mmcif } = await readCIF('./examples/1cbs_full.bcif');
-        //const { structures, models, mmcif } = await readCIF('e:/test/quick/3j3q_full.bcif');
+        const { structures, models } = await readCIF('e:/test/quick/3j3q_full.bcif');
         //const { structures, models, mmcif } = await readCIF('e:/test/quick/1cbs_updated.cif');
-        const { structures, models/*, mmcif*/ } = await readCIF('e:/test/quick/5j7v_updated.cif');
+        //const { structures, models/*, mmcif*/ } = await readCIF('e:/test/quick/5j7v_updated.cif');
 
         //console.log(mmcif.pdbx_struct_oper_list.matrix.toArray());
         // console.log(mmcif.pdbx_struct_oper_list.vector.toArray());
 
-        testAssembly('5j7v', structures[0]);
-        throw '';
+        // testAssembly('5j7v', structures[0]);
+        // throw '';
 
-        console.log(models[0].symmetry.assemblies);
+        // console.log(models[0].symmetry.assemblies);
 
 
         //const { structures, models } = await readCIF('e:/test/molstar/3j3q.bcif');
-- 
GitLab