Ver Fonte

More atom set refactoring

David Sehnal há 7 anos atrás
pai
commit
9a3b83d136

+ 0 - 193
src/mol-model/structure/_spec/atom-set.1.spec.ts

@@ -1,193 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { OrderedSet } from 'mol-data/int'
-import AtomSet from '../structure/atom/set.1'
-import Atom from '../structure/atom'
-import AtomGroup from '../structure/atom/group'
-
-describe('atom set', () => {
-    const p = (i: number, j: number) => Atom.create(i, j);
-
-    function setToPairs(set: AtomSet): ArrayLike<Atom> {
-        const ret: Atom[] = [];
-        const it = AtomSet.atoms(set);
-        while (it.hasNext) {
-            ret[ret.length] = it.move();
-        }
-        return ret;
-    }
-
-    it('singleton pair', () => {
-        const set = AtomSet.ofAtoms([p(10, 11)], AtomSet.Empty);
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(AtomSet.atomHas(set, p(10, 11))).toBe(true);
-        expect(AtomSet.atomHas(set, p(11, 11))).toBe(false);
-        expect(AtomSet.atomGetAt(set, 0)).toBe(p(10, 11));
-        expect(AtomSet.atomCount(set)).toBe(1);
-    });
-
-    it('multi', () => {
-        const gen = AtomSet.Generator();
-        gen.add(1, AtomGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7])));
-        gen.add(3, AtomGroup.createNew(OrderedSet.ofRange(0, 1)));
-        const set = gen.getSet();
-        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.atomHas(set, p(10, 11))).toBe(false);
-        expect(AtomSet.atomHas(set, p(3, 0))).toBe(true);
-        expect(AtomSet.atomHas(set, p(1, 7))).toBe(true);
-        for (let i = 0; i < AtomSet.atomCount(set); i++) {
-            expect(Atom.areEqual(AtomSet.atomGetAt(set, i), ret[i])).toBe(true);
-        }
-    });
-
-    it('template', () => {
-        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
-        const gen = AtomSet.TemplateGenerator(template);
-        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
-        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(3)));
-        const set = gen.getSet();
-
-        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
-        expect(AtomSet.unitGetById(set, 1)).toBe(AtomSet.unitGetById(template, 1));
-        expect(set).toBe(template);
-    });
-
-    it('template 1', () => {
-        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
-        const gen = AtomSet.TemplateGenerator(template);
-        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
-        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(4)));
-        const set = gen.getSet();
-
-        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
-        expect(AtomSet.unitGetById(set, 1) === AtomSet.unitGetById(template, 1)).toBe(false);
-        expect(set === template).toBe(false);
-    });
-
-    it('template union', () => {
-        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
-
-        const p13 = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const p01 = AtomSet.ofAtoms([p(0, 1)], AtomSet.Empty);
-        const p02 = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
-        const p06 = AtomSet.ofAtoms([p(0, 6)], AtomSet.Empty);
-
-        const u0 = AtomSet.union([p01, p02, p06], template);
-        const u1 = AtomSet.union([p01, p02, p06, p13], template);
-        expect(AtomSet.unitGetById(u0, 0)).toBe(AtomSet.unitGetById(template, 0));
-        expect(AtomSet.unitGetById(u1, 0)).toBe(AtomSet.unitGetById(template, 0));
-        expect(AtomSet.unitGetById(u1, 1)).toBe(AtomSet.unitGetById(template, 1));
-        expect(u1).toBe(template);
-    });
-
-    it('element at / index of', () => {
-        const control: Atom[] = [];
-        const gen = AtomSet.Generator();
-        for (let i = 1; i < 10; i++) {
-            const set = [];
-            for (let j = 1; j < 7; j++) {
-                control[control.length] = p(i * i, j * j + 1);
-                set[set.length] = j * j + 1;
-            }
-            gen.add(i * i, AtomGroup.createNew(OrderedSet.ofSortedArray(set)));
-        }
-        const ms = gen.getSet();
-        for (let i = 0; i < control.length; i++) {
-            expect(Atom.areEqual(AtomSet.atomGetAt(ms, i), control[i])).toBe(true);
-        }
-
-        for (let i = 0; i < control.length; i++) {
-            expect(AtomSet.atomIndexOf(ms, control[i])).toBe(i);
-        }
-    });
-
-    it('packed pairs', () => {
-        const set = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
-    });
-
-    it('equality', () => {
-        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
-
-        expect(AtomSet.areEqual(a, a)).toBe(true);
-        expect(AtomSet.areEqual(a, b)).toBe(true);
-        expect(AtomSet.areEqual(a, c)).toBe(false);
-        expect(AtomSet.areEqual(a, d)).toBe(false);
-        expect(AtomSet.areEqual(d, d)).toBe(true);
-        expect(AtomSet.areEqual(d, e)).toBe(true);
-        expect(AtomSet.areEqual(d, f)).toBe(false);
-    });
-
-    it('are intersecting', () => {
-        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
-        const g = AtomSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], AtomSet.Empty);
-
-        expect(AtomSet.areIntersecting(a, a)).toBe(true);
-        expect(AtomSet.areIntersecting(a, b)).toBe(true);
-        expect(AtomSet.areIntersecting(a, c)).toBe(true);
-        expect(AtomSet.areIntersecting(a, d)).toBe(true);
-        expect(AtomSet.areIntersecting(a, g)).toBe(false);
-        expect(AtomSet.areIntersecting(d, d)).toBe(true);
-        expect(AtomSet.areIntersecting(d, e)).toBe(true);
-        expect(AtomSet.areIntersecting(d, f)).toBe(false);
-    });
-
-    it('intersection', () => {
-        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
-        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
-        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)]);
-        expect(setToPairs(AtomSet.intersect(c, d))).toEqual([]);
-    });
-
-    it('subtract', () => {
-        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
-        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
-        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
-        const e = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
-        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)]);
-        expect(setToPairs(AtomSet.subtract(c, d))).toEqual([p(1, 3)]);
-        expect(setToPairs(AtomSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]);
-        expect(setToPairs(AtomSet.subtract(c, a))).toEqual([]);
-        expect(setToPairs(AtomSet.subtract(d, a))).toEqual([p(2, 3)]);
-        expect(setToPairs(AtomSet.subtract(a, e))).toEqual([p(0, 1), p(0, 6), p(1, 3)]);
-    });
-
-    it('union', () => {
-        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
-        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
-        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1)], AtomSet.Empty);
-        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
-        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
-        expect(AtomSet.union([a], AtomSet.Empty)).toBe(a);
-        expect(AtomSet.union([a, a], AtomSet.Empty)).toBe(a);
-        expect(setToPairs(AtomSet.union([a, a], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(AtomSet.union([a, a1], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(AtomSet.union([a, b], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
-        expect(setToPairs(AtomSet.union([c, d], AtomSet.Empty))).toEqual([p(1, 3), p(2, 3)]);
-        expect(setToPairs(AtomSet.union([a, b, c, d], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
-    });
-});

+ 81 - 45
src/mol-model/structure/_spec/atom-set.spec.ts

@@ -7,6 +7,7 @@
 import { OrderedSet } from 'mol-data/int'
 import AtomSet from '../structure/atom/set'
 import Atom from '../structure/atom'
+import AtomGroup from '../structure/atom/group'
 
 describe('atom set', () => {
     const p = (i: number, j: number) => Atom.create(i, j);
@@ -21,7 +22,7 @@ describe('atom set', () => {
     }
 
     it('singleton pair', () => {
-        const set = AtomSet.create(p(10, 11));
+        const set = AtomSet.ofAtoms([p(10, 11)], AtomSet.Empty);
         expect(setToPairs(set)).toEqual([p(10, 11)]);
         expect(AtomSet.atomHas(set, p(10, 11))).toBe(true);
         expect(AtomSet.atomHas(set, p(11, 11))).toBe(false);
@@ -29,15 +30,10 @@ describe('atom set', () => {
         expect(AtomSet.atomCount(set)).toBe(1);
     });
 
-    it('singleton number', () => {
-        const set = AtomSet.create(p(10, 11));
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-    });
-
     it('multi', () => {
         const gen = AtomSet.Generator();
-        gen.add(1, OrderedSet.ofSortedArray([4, 6, 7]));
-        gen.add(3, OrderedSet.ofRange(0, 1));
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7])));
+        gen.add(3, AtomGroup.createNew(OrderedSet.ofRange(0, 1)));
         const set = gen.getSet();
         const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)];
         expect(AtomSet.atomCount(set)).toBe(ret.length);
@@ -50,6 +46,46 @@ describe('atom set', () => {
         }
     });
 
+    it('template', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+        const gen = AtomSet.TemplateGenerator(template);
+        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(3)));
+        const set = gen.getSet();
+
+        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(set, 1)).toBe(AtomSet.unitGetById(template, 1));
+        expect(set).toBe(template);
+    });
+
+    it('template 1', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+        const gen = AtomSet.TemplateGenerator(template);
+        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(4)));
+        const set = gen.getSet();
+
+        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(set, 1) === AtomSet.unitGetById(template, 1)).toBe(false);
+        expect(set === template).toBe(false);
+    });
+
+    it('template union', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+
+        const p13 = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const p01 = AtomSet.ofAtoms([p(0, 1)], AtomSet.Empty);
+        const p02 = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
+        const p06 = AtomSet.ofAtoms([p(0, 6)], AtomSet.Empty);
+
+        const u0 = AtomSet.union([p01, p02, p06], template);
+        const u1 = AtomSet.union([p01, p02, p06, p13], template);
+        expect(AtomSet.unitGetById(u0, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(u1, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(u1, 1)).toBe(AtomSet.unitGetById(template, 1));
+        expect(u1).toBe(template);
+    });
+
     it('element at / index of', () => {
         const control: Atom[] = [];
         const gen = AtomSet.Generator();
@@ -59,7 +95,7 @@ describe('atom set', () => {
                 control[control.length] = p(i * i, j * j + 1);
                 set[set.length] = j * j + 1;
             }
-            gen.add(i * i, OrderedSet.ofSortedArray(set));
+            gen.add(i * i, AtomGroup.createNew(OrderedSet.ofSortedArray(set)));
         }
         const ms = gen.getSet();
         for (let i = 0; i < control.length; i++) {
@@ -72,17 +108,17 @@ describe('atom set', () => {
     });
 
     it('packed pairs', () => {
-        const set = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]);
+        const set = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
         expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
     });
 
     it('equality', () => {
-        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 a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
 
         expect(AtomSet.areEqual(a, a)).toBe(true);
         expect(AtomSet.areEqual(a, b)).toBe(true);
@@ -94,13 +130,13 @@ describe('atom set', () => {
     });
 
     it('are intersecting', () => {
-        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)]);
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
+        const g = AtomSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], AtomSet.Empty);
 
         expect(AtomSet.areIntersecting(a, a)).toBe(true);
         expect(AtomSet.areIntersecting(a, b)).toBe(true);
@@ -113,10 +149,10 @@ describe('atom set', () => {
     });
 
     it('intersection', () => {
-        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)]);
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
         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)]);
@@ -124,12 +160,12 @@ describe('atom set', () => {
     });
 
     it('subtract', () => {
-        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)]);
-        const e = AtomSet.create([p(0, 2)]);
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
         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)]);
@@ -141,17 +177,17 @@ describe('atom set', () => {
     });
 
     it('union', () => {
-        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)]);
-        expect(setToPairs(AtomSet.union(a, a1))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(AtomSet.union(a, b))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
-        expect(setToPairs(AtomSet.union(c, d))).toEqual([p(1, 3), p(2, 3)]);
-        expect(setToPairs(AtomSet.unionMany([a, b, c, d]))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
+        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
+        expect(AtomSet.union([a], AtomSet.Empty)).toBe(a);
+        expect(AtomSet.union([a, a], AtomSet.Empty)).toBe(a);
+        expect(setToPairs(AtomSet.union([a, a], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
+        expect(setToPairs(AtomSet.union([a, a1], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
+        expect(setToPairs(AtomSet.union([a, b], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
+        expect(setToPairs(AtomSet.union([c, d], AtomSet.Empty))).toEqual([p(1, 3), p(2, 3)]);
+        expect(setToPairs(AtomSet.union([a, b, c, d], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
     });
 });

+ 4 - 4
src/mol-model/structure/query/generators.ts

@@ -46,7 +46,7 @@ function atomGroupsLinear(atomTest: Atom.Predicate): Query {
         for (let i = 0, _i = unitIds.length; i < _i; i++) {
             const unitId = unitIds[i];
             l.unit = units[unitId];
-            const set = AtomSet.unitGetByIndex(atoms, i);
+            const set = AtomSet.unitGetByIndex(atoms, i).atoms;
 
             builder.beginUnit();
             for (let j = 0, _j = OrderedSet.size(set); j < _j; j++) {
@@ -71,7 +71,7 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
             const unitId = unitIds[i];
             const unit = units[unitId];
             l.unit = unit;
-            const set = AtomSet.unitGetByIndex(atoms, i);
+            const set = AtomSet.unitGetByIndex(atoms, i).atoms;
 
             builder.beginUnit();
             const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
@@ -129,7 +129,7 @@ class LinearGroupingBuilder {
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
             atoms[i] = this.builders[i].singleton();
         }
-        return Structure.create(this.structure.units, AtomSet.create(atoms));
+        return Structure.create(this.structure.units, AtomSet.ofAtoms(atoms, this.structure.atoms));
     }
 
     private fullSelection() {
@@ -162,7 +162,7 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
             const unitId = unitIds[i];
             const unit = units[unitId];
             l.unit = unit;
-            const set = AtomSet.unitGetByIndex(atoms, i);
+            const set = AtomSet.unitGetByIndex(atoms, i).atoms;
 
             const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
             const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);

+ 1 - 1
src/mol-model/structure/query/selection.ts

@@ -38,7 +38,7 @@ namespace Selection {
         if (!sel.length) return Structure.Empty;
         const sets = [];
         for (let i = 0, _i = sel.length; i < _i; i++) sets[sets.length] = sel[i].atoms;
-        return Structure.create(unionUnits(sel), AtomSet.unionMany(sets));
+        return Structure.create(unionUnits(sel), AtomSet.union(sets, AtomSet.Empty));
     }
 
     export function structures(sel: Selection): Iterator<Structure> {

+ 5 - 0
src/mol-model/structure/structure/atom/group.ts

@@ -28,6 +28,11 @@ namespace AtomGroup {
         return createNew(atoms);
     }
 
+    export function createChild(parent: AtomGroup, atoms: OrderedSet): AtomGroup {
+        if (OrderedSet.areEqual(atoms, parent.atoms)) return parent;
+        return createNew(atoms);
+    }
+
     export function size(group: AtomGroup) { return OrderedSet.size(group.atoms); }
     export function has(group: AtomGroup, atom: number) { return OrderedSet.has(group.atoms, atom); }
     export function getAt(group: AtomGroup, i: number) { return OrderedSet.getAt(group.atoms, i); }

+ 5 - 8
src/mol-model/structure/structure/atom/impl/builder.ts

@@ -6,6 +6,7 @@
 
 import AtomSet from '../set'
 import Atom from '../../atom'
+import AtomGroup from '../group'
 import { OrderedSet, IntMap } from 'mol-data/int'
 import { sortArray } from 'mol-data/util/sort'
 
@@ -45,14 +46,10 @@ export class Builder {
             const l = unit.length;
             if (!this.sorted && l > 1) sortArray(unit);
 
-            const set = OrderedSet.ofSortedArray(unit);
-            const parentSet = AtomSet.unitGetById(this.parent, k);
-            if (OrderedSet.areEqual(set, parentSet)) {
-                generator.add(k, parentSet);
-            } else {
-                generator.add(k, set);
-                allEqual = false;
-            }
+            const parentGroup = AtomSet.unitGetById(this.parent, k);
+            const group = AtomGroup.createChild(parentGroup, OrderedSet.ofSortedArray(unit));
+            if (group !== parentGroup) allEqual = false;
+            generator.add(k, group);
         }
         return allEqual ? this.parent : generator.getSet();
     }

+ 0 - 426
src/mol-model/structure/structure/atom/impl/set.1.ts

@@ -1,426 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { SortedArray, Interval, Iterator, OrderedSet as OS, IntMap } from 'mol-data/int'
-import { sortArray } from 'mol-data/util/sort'
-import { hash1 } from 'mol-data/util/hash-functions'
-import Atom from '../../atom'
-import AtomGroup from '../group'
-
-/** Long and painful implementation starts here */
-
-export type AtomSetImpl = { groups: IntMap<AtomGroup>, offsets: Int32Array, hashCode: number, keys: SortedArray }
-
-export const Empty: AtomSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
-
-export function ofAtoms(atoms: ArrayLike<Atom>, template: AtomSetImpl): AtomSetImpl {
-    return ofAtomsImpl(atoms, template);
-}
-
-export function getKeys(set: AtomSetImpl): SortedArray {
-    return set.keys;
-}
-
-export function keyCount(set: AtomSetImpl): number {
-    return set.keys.length;
-}
-
-export function hasKey(set: AtomSetImpl, key: number): boolean {
-    return set.groups.has(key);
-}
-
-export function getKey(set: AtomSetImpl, index: number): number {
-    return set.keys[index];
-}
-
-export function hasAtom(set: AtomSetImpl, t: Atom): boolean {
-    const os = set.groups.get(Atom.unit(t));
-    return !!os && AtomGroup.has(os, Atom.index(t));
-}
-
-export function getByKey(set: AtomSetImpl, key: number): AtomGroup {
-    return set.groups.get(key) || AtomGroup.Empty;
-}
-
-export function getByIndex(set: AtomSetImpl, index: number): AtomGroup {
-    const key = set.keys[index];
-    return set.groups.get(key) || AtomGroup.Empty;
-}
-
-export function getAt(set: AtomSetImpl, i: number): Atom {
-    const { offsets, keys } = set;
-    const o = getOffsetIndex(offsets, i);
-    if (o >= offsets.length - 1) return Atom.Zero;
-    const k = keys[o];
-    const e = AtomGroup.getAt(set.groups.get(k), i - offsets[o]);
-    return Atom.create(k, e);
-}
-
-export function indexOf(set: AtomSetImpl, t: Atom) {
-    const { keys } = set;
-    const u = Atom.unit(t);
-    const setIdx = SortedArray.indexOf(keys, u);
-    if (setIdx < 0) return -1;
-    const o = AtomGroup.indexOf(set.groups.get(u), Atom.index(t));
-    if (o < 0) return -1;
-    return set.offsets[setIdx] + o;
-}
-
-/** Number elements in the "child" sets */
-export function size(set: AtomSetImpl) {
-    return set.offsets[set.offsets.length - 1];
-}
-
-export function hashCode(set: AtomSetImpl) {
-    if (set.hashCode !== -1) return set.hashCode;
-    return computeHash(set);
-}
-
-export function areEqual(a: AtomSetImpl, b: AtomSetImpl) {
-    if (a === b) return true;
-    if (size(a) !== size(a)) return false;
-
-    const keys = a.keys;
-    if (!SortedArray.areEqual(keys, b.keys)) return false;
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        if (!AtomGroup.areEqual(aG.get(k), bG.get(k))) return false;
-    }
-    return true;
-}
-
-export function areIntersecting(a: AtomSetImpl, b: AtomSetImpl) {
-    if (a === b) return true;
-    const keysA = a.keys, keysB = b.keys;
-    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 { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k), bk = bG.get(k);
-        if (!!ak && !!bk && OS.areIntersecting(ak.atoms, bk.atoms)) return true;
-    }
-    return false;
-}
-
-export function intersect(a: AtomSetImpl, b: AtomSetImpl) {
-    if (a === b) return a;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    const generator = new ChildGenerator(a, b);
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const bk = bG.get(k);
-        if (!bk) continue;
-        const ak = aG.get(k);
-        generator.add(k, AtomGroup.intersect(aG.get(k), bk), ak, bk);
-    }
-    return generator.getSet();
-}
-
-export function subtract(a: AtomSetImpl, b: AtomSetImpl) {
-    if (a === b) return Empty;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(keysA, keysB)) return a;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-
-    const generator = new ChildGenerator(a, b);
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = 0; i < start; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k);
-        generator.addA(k, ak, ak);
-    }
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k), bk = bG.get(k);
-        if (!!bk) {
-            const subtraction = AtomGroup.subtract(ak, bk);
-            generator.addA(k, subtraction, ak);
-        } else {
-            generator.addA(k, ak, ak);
-        }
-    }
-    for (let i = end, _i = keysA.length; i < _i; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k);
-        generator.addA(k, ak, ak);
-    }
-    return generator.getSet();
-}
-
-export function unionMany(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) {
-    return findUnion(sets, template);
-}
-
-class ElementsIterator implements Iterator<Atom> {
-    private unit: number = 0;
-    private keyCount: number;
-    private setIndex = -1;
-    private currentIndex = 0;
-    private currentSize = 0;
-    private currentSet: OS = OS.Empty;
-
-    hasNext: boolean = false;
-
-    move() {
-        if (!this.hasNext) return Atom.Zero;
-        const ret = Atom.create(this.unit, OS.getAt(this.currentSet, this.currentIndex++));
-        if (this.currentIndex >= this.currentSize) this.advance();
-        return ret;
-    }
-
-    private advance() {
-        if (++this.setIndex >= this.keyCount) {
-            this.hasNext = false;
-            return false;
-        }
-        this.unit = this.elements.keys[this.setIndex];
-        this.currentSet = this.elements.groups.get(this.unit).atoms;
-        this.currentIndex = 0;
-        this.currentSize = OS.size(this.currentSet);
-        return true;
-    }
-
-    constructor(private elements: AtomSetImpl) {
-        this.keyCount = elements.keys.length;
-        this.hasNext = this.keyCount > 0;
-        this.advance();
-    }
-}
-
-export function values(set: AtomSetImpl): Iterator<Atom> {
-    return new ElementsIterator(set);
-}
-
-export class TemplateAtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<AtomGroup>();
-    private templateGroups: IntMap<AtomGroup>;
-    private equalGroups = 0;
-
-    add(unit: number, group: AtomGroup) {
-        if (AtomGroup.size(group) === 0) return;
-        this.keys[this.keys.length] = unit;
-        let t: AtomGroup;
-        if (this.templateGroups.has(unit) && AtomGroup.areEqual(t = this.templateGroups.get(unit), group)) {
-            this.groups.set(unit, t);
-            this.equalGroups++;
-        } else {
-            this.groups.set(unit, group);
-        }
-    }
-
-    getSet(): AtomSetImpl {
-        if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) {
-            return this.template;
-        }
-        return create(this.keys, this.groups);
-    }
-
-    constructor(private template: AtomSetImpl) {
-        this.templateGroups = template.groups;
-    }
-}
-
-export function TemplateGenerator(template: AtomSetImpl) {
-    return new TemplateAtomSetGenerator(template);
-}
-
-export class AtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<AtomGroup>();
-
-    add(unit: number, group: AtomGroup) {
-        if (AtomGroup.size(group) === 0) return;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    getSet(): AtomSetImpl {
-        return create(this.keys, this.groups);
-    }
-}
-
-export function Generator() {
-    return new AtomSetGenerator();
-}
-
-/** When adding groups, compare them to existing ones. If they all match, return the whole original set. */
-class ChildGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<AtomGroup>();
-    private aEqual = 0;
-    private bEqual = 0;
-
-    add(unit: number, group: AtomGroup, a: AtomGroup, b: AtomGroup) {
-        if (AtomGroup.size(group) === 0) return;
-        if (a === group) this.aEqual++;
-        if (b === group) this.bEqual++;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    addA(unit: number, group: AtomGroup, a: AtomGroup) {
-        if (AtomGroup.size(group) === 0) return;
-
-        if (a === group) this.aEqual++;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    constructor(private a: AtomSetImpl, private b: AtomSetImpl) {
-    }
-
-    getSet(): AtomSetImpl {
-        if (this.aEqual === this.a.keys.length) return this.a;
-        if (this.bEqual === this.b.keys.length) return this.b;
-        return create(this.keys, this.groups);
-    }
-}
-
-function create(keys: number[], groups: IntMap<AtomGroup>): AtomSetImpl {
-    sortArray(keys);
-    let runningSize = 0;
-    const offsets = new Int32Array(keys.length + 1);
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        runningSize += AtomGroup.size(groups.get(keys[i]));
-        offsets[i + 1] = runningSize;
-    }
-    return { keys: SortedArray.ofSortedArray(keys), groups: IntMap.asImmutable(groups), offsets, hashCode: -1 };
-}
-
-function getUniqueElements(xs: number[]): 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[]): 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 ofAtomsImpl(xs: ArrayLike<Atom>, template: AtomSetImpl) {
-    if (xs.length === 0) return Empty;
-
-    const elements = IntMap.Mutable<number[]>();
-    const keys: number[] = [];
-    for (let i = 0, _i = xs.length; i < _i; i++) {
-        const x = xs[i];
-        const u = Atom.unit(x), v = Atom.index(x);
-        if (elements.has(u)) {
-            const set = elements.get(u);
-            set[set.length] = v;
-        } else {
-            keys[keys.length] = u;
-            elements.set(u, [v]);
-        }
-    }
-
-    const generator = TemplateGenerator(template);
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        const group = AtomGroup.createNew(OS.ofSortedArray(normalizeArray(elements.get(k))));
-        generator.add(k, group);
-    }
-
-    return generator.getSet();
-}
-
-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 computeHash(set: AtomSetImpl) {
-    const { keys, groups } = 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 + AtomGroup.hashCode(groups.get(k))) | 0;
-    }
-    hash = (31 * hash + size(set)) | 0;
-    hash = hash1(hash);
-    set.hashCode = hash;
-    return hash;
-}
-
-function findUnion(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) {
-    if (!sets.length) return Empty;
-    if (sets.length === 1) return sets[0];
-    if (sets.length === 2 && sets[0] === sets[1]) return sets[0];
-
-    const keys: number[] = [];
-    const groups = IntMap.Mutable<AtomGroup>();
-    for (let i = 0, _i = sets.length; i < _i; i++) {
-        unionInto(keys, groups, sets[i]);
-    }
-
-    return normalizeUnion(keys, groups, template);
-}
-
-function normalizeUnion(keys: number[], groups: IntMap.Mutable<AtomGroup>, template: AtomSetImpl) {
-    let equalCount = 0;
-    let tg = template.groups, a: AtomGroup, t: AtomGroup;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        if (tg.has(k) && AtomGroup.areEqual(a = groups.get(k), t = tg.get(k))) {
-            groups.set(k, t);
-            equalCount++;
-        }
-    }
-    return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups);
-}
-
-function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSetImpl) {
-    const setKeys = a.keys;
-    const { groups: aG } = a;
-    for (let i = 0, _i = setKeys.length; i < _i; i++) {
-        const k = setKeys[i];
-        if (groups.has(k)) {
-            groups.set(k, AtomGroup.union(aG.get(k), groups.get(k)))
-        } else {
-            keys[keys.length] = k;
-            groups.set(k, aG.get(k));
-        }
-    }
-}

+ 218 - 281
src/mol-model/structure/structure/atom/impl/set.ts

@@ -4,126 +4,169 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { SortedArray, Interval, Iterator, OrderedSet, IntMap } from 'mol-data/int'
+import { SortedArray, Interval, Iterator, OrderedSet as OS, IntMap } from 'mol-data/int'
 import { sortArray } from 'mol-data/util/sort'
 import { hash1 } from 'mol-data/util/hash-functions'
 import Atom from '../../atom'
+import AtomGroup from '../group'
 
 /** Long and painful implementation starts here */
 
-export interface AtomSetElements { sets: IntMap<OrderedSet>, offsets: Int32Array, hashCode: number, keys: SortedArray }
-export type AtomSetImpl = Atom | AtomSetElements
+export type AtomSetImpl = { groups: IntMap<AtomGroup>, offsets: Int32Array, hashCode: number, keys: SortedArray }
 
-export const Empty: AtomSetImpl = { sets: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
+export const Empty: AtomSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
 
-export function create(data: Atom | ArrayLike<Atom>): AtomSetImpl {
-    if (Atom.is(data)) return data;
-    return ofAtoms(data);
+export function ofAtoms(atoms: ArrayLike<Atom>, template: AtomSetImpl): AtomSetImpl {
+    return ofAtomsImpl(atoms, template);
 }
 
 export function getKeys(set: AtomSetImpl): SortedArray {
-    if (typeof set === 'number') return SortedArray.ofSingleton(set);
-    return (set as AtomSetElements).keys;
+    return set.keys;
 }
 
 export function keyCount(set: AtomSetImpl): number {
-    if (typeof set === 'number') return 1;
-    return (set as AtomSetElements).keys.length;
+    return set.keys.length;
 }
 
 export function hasKey(set: AtomSetImpl, key: number): boolean {
-    if (typeof set === 'number') return Atom.unit(set) === key;
-    return !!(set as AtomSetElements).sets.has(key);
+    return set.groups.has(key);
 }
 
 export function getKey(set: AtomSetImpl, index: number): number {
-    if (typeof set === 'number') return Atom.unit(set);
-    return (set as AtomSetElements).keys[index];
+    return set.keys[index];
 }
 
 export function hasAtom(set: AtomSetImpl, t: Atom): boolean {
-    if (typeof set === 'number') return Atom.areEqual(t, set);
-    const os = (set as AtomSetElements).sets.get(Atom.unit(t));
-    return !!os && OrderedSet.has(os, Atom.index(t));
+    const os = set.groups.get(Atom.unit(t));
+    return !!os && AtomGroup.has(os, Atom.index(t));
 }
 
-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).sets.get(key) || OrderedSet.Empty;
+export function getByKey(set: AtomSetImpl, key: number): AtomGroup {
+    return set.groups.get(key) || AtomGroup.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).sets.get(key) || OrderedSet.Empty;
+export function getByIndex(set: AtomSetImpl, index: number): AtomGroup {
+    const key = set.keys[index];
+    return set.groups.get(key) || AtomGroup.Empty;
 }
 
 export function getAt(set: AtomSetImpl, i: number): Atom {
-    if (typeof set === 'number') return set;
-    return getAtE(set as AtomSetElements, i);
+    const { offsets, keys } = set;
+    const o = getOffsetIndex(offsets, i);
+    if (o >= offsets.length - 1) return Atom.Zero;
+    const k = keys[o];
+    const e = AtomGroup.getAt(set.groups.get(k), i - offsets[o]);
+    return Atom.create(k, e);
 }
 
 export function indexOf(set: AtomSetImpl, t: Atom) {
-    if (typeof set === 'number') return Atom.areEqual(set, t) ? 0 : -1;
-    return indexOfE(set as AtomSetElements, t);
+    const { keys } = set;
+    const u = Atom.unit(t);
+    const setIdx = SortedArray.indexOf(keys, u);
+    if (setIdx < 0) return -1;
+    const o = AtomGroup.indexOf(set.groups.get(u), Atom.index(t));
+    if (o < 0) return -1;
+    return set.offsets[setIdx] + o;
 }
 
 /** 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];
+    return set.offsets[set.offsets.length - 1];
 }
 
 export function hashCode(set: AtomSetImpl) {
-    if (typeof set === 'number') return Atom.hashCode(set);
-    if ((set as AtomSetElements).hashCode !== -1) return (set as AtomSetElements).hashCode;
-    return computeHash((set as AtomSetElements));
+    if (set.hashCode !== -1) return set.hashCode;
+    return computeHash(set);
 }
 
 export function areEqual(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return Atom.areEqual(a, b);
-        return false;
+    if (a === b) return true;
+    if (size(a) !== size(a)) return false;
+
+    const keys = a.keys;
+    if (!SortedArray.areEqual(keys, b.keys)) return false;
+    const { groups: aG } = a;
+    const { groups: bG } = b;
+    for (let i = 0, _i = keys.length; i < _i; i++) {
+        const k = keys[i];
+        if (!AtomGroup.areEqual(aG.get(k), bG.get(k))) return false;
     }
-    if (typeof b === 'number') return false;
-    return areEqualEE(a as AtomSetElements, b as AtomSetElements);
+    return true;
 }
 
 export function areIntersecting(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return Atom.areEqual(a, b);
-        return areIntersectingNE(a, b as AtomSetElements);
+    if (a === b) return true;
+    const keysA = a.keys, keysB = b.keys;
+    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 { groups: aG } = a;
+    const { groups: bG } = b;
+    for (let i = start; i < end; i++) {
+        const k = keysA[i];
+        const ak = aG.get(k), bk = bG.get(k);
+        if (!!ak && !!bk && OS.areIntersecting(ak.atoms, bk.atoms)) return true;
     }
-    if (typeof b === 'number') return areIntersectingNE(b, a as AtomSetElements);
-    return areIntersectingEE(a as AtomSetElements, b as AtomSetElements);
+    return false;
 }
 
 export function intersect(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return Atom.areEqual(a, b) ? a : Empty;
-        return intersectNE(a, b as AtomSetElements);
+    if (a === b) return a;
+
+    const keysA = a.keys, keysB = b.keys;
+    if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty;
+    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
+    const start = Interval.start(r), end = Interval.end(r);
+
+    const { groups: aG } = a;
+    const { groups: bG } = b;
+    const generator = new ChildGenerator(a, b);
+    for (let i = start; i < end; i++) {
+        const k = keysA[i];
+        const bk = bG.get(k);
+        if (!bk) continue;
+        const ak = aG.get(k);
+        generator.add(k, AtomGroup.intersect(aG.get(k), bk), ak, bk);
     }
-    if (typeof b === 'number') return intersectNE(b, a as AtomSetElements);
-    return intersectEE(a as AtomSetElements, b as AtomSetElements);
+    return generator.getSet();
 }
 
 export function subtract(a: AtomSetImpl, b: AtomSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return Atom.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);
-}
+    if (a === b) return Empty;
+
+    const keysA = a.keys, keysB = b.keys;
+    if (!SortedArray.areIntersecting(keysA, keysB)) return a;
+    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
+    const start = Interval.start(r), end = Interval.end(r);
 
-export function union(a: AtomSetImpl, b: AtomSetImpl) {
-    return findUnion([a, b]);
+    const generator = new ChildGenerator(a, b);
+    const { groups: aG } = a;
+    const { groups: bG } = b;
+    for (let i = 0; i < start; i++) {
+        const k = keysA[i];
+        const ak = aG.get(k);
+        generator.addA(k, ak, ak);
+    }
+    for (let i = start; i < end; i++) {
+        const k = keysA[i];
+        const ak = aG.get(k), bk = bG.get(k);
+        if (!!bk) {
+            const subtraction = AtomGroup.subtract(ak, bk);
+            generator.addA(k, subtraction, ak);
+        } else {
+            generator.addA(k, ak, ak);
+        }
+    }
+    for (let i = end, _i = keysA.length; i < _i; i++) {
+        const k = keysA[i];
+        const ak = aG.get(k);
+        generator.addA(k, ak, ak);
+    }
+    return generator.getSet();
 }
 
-export function unionMany(sets: ArrayLike<AtomSetImpl>) {
-    return findUnion(sets);
+export function unionMany(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) {
+    return findUnion(sets, template);
 }
 
 class ElementsIterator implements Iterator<Atom> {
@@ -132,13 +175,13 @@ class ElementsIterator implements Iterator<Atom> {
     private setIndex = -1;
     private currentIndex = 0;
     private currentSize = 0;
-    private currentSet: OrderedSet = OrderedSet.Empty;
+    private currentSet: OS = OS.Empty;
 
     hasNext: boolean = false;
 
     move() {
         if (!this.hasNext) return Atom.Zero;
-        const ret = Atom.create(this.unit, OrderedSet.getAt(this.currentSet, this.currentIndex++));
+        const ret = Atom.create(this.unit, OS.getAt(this.currentSet, this.currentIndex++));
         if (this.currentIndex >= this.currentSize) this.advance();
         return ret;
     }
@@ -149,13 +192,13 @@ class ElementsIterator implements Iterator<Atom> {
             return false;
         }
         this.unit = this.elements.keys[this.setIndex];
-        this.currentSet = this.elements.sets.get(this.unit);
+        this.currentSet = this.elements.groups.get(this.unit).atoms;
         this.currentIndex = 0;
-        this.currentSize = OrderedSet.size(this.currentSet);
+        this.currentSize = OS.size(this.currentSet);
         return true;
     }
 
-    constructor(private elements: AtomSetElements) {
+    constructor(private elements: AtomSetImpl) {
         this.keyCount = elements.keys.length;
         this.hasNext = this.keyCount > 0;
         this.advance();
@@ -163,33 +206,55 @@ class ElementsIterator implements Iterator<Atom> {
 }
 
 export function values(set: AtomSetImpl): Iterator<Atom> {
-    if (typeof set === 'number') return Iterator.Value(set as Atom);
-    return new ElementsIterator(set as AtomSetElements);
+    return new ElementsIterator(set);
 }
 
-export class AtomSetGenerator {
+export class TemplateAtomSetGenerator {
     private keys: number[] = [];
-    private sets = IntMap.Mutable<OrderedSet>();
+    private groups = IntMap.Mutable<AtomGroup>();
+    private templateGroups: IntMap<AtomGroup>;
+    private equalGroups = 0;
 
-    add(unit: number, set: OrderedSet) {
-        if (OrderedSet.size(set) === 0) return;
+    add(unit: number, group: AtomGroup) {
+        if (AtomGroup.size(group) === 0) return;
         this.keys[this.keys.length] = unit;
-        this.sets.set(unit, set);
+        let t: AtomGroup;
+        if (this.templateGroups.has(unit) && AtomGroup.areEqual(t = this.templateGroups.get(unit), group)) {
+            this.groups.set(unit, t);
+            this.equalGroups++;
+        } else {
+            this.groups.set(unit, group);
+        }
     }
 
-    addUnion(unit: number, set: OrderedSet) {
-        if (OrderedSet.size(set) === 0) return;
-
-        if (this.sets.has(unit)) {
-            this.sets.set(unit, OrderedSet.union(this.sets.get(unit), set));
-        } else {
-            this.keys[this.keys.length] = unit;
-            this.sets.set(unit, set);
+    getSet(): AtomSetImpl {
+        if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) {
+            return this.template;
         }
+        return create(this.keys, this.groups);
+    }
+
+    constructor(private template: AtomSetImpl) {
+        this.templateGroups = template.groups;
+    }
+}
+
+export function TemplateGenerator(template: AtomSetImpl) {
+    return new TemplateAtomSetGenerator(template);
+}
+
+export class AtomSetGenerator {
+    private keys: number[] = [];
+    private groups = IntMap.Mutable<AtomGroup>();
+
+    add(unit: number, group: AtomGroup) {
+        if (AtomGroup.size(group) === 0) return;
+        this.keys[this.keys.length] = unit;
+        this.groups.set(unit, group);
     }
 
     getSet(): AtomSetImpl {
-        return ofKeysAndSets(this.keys, this.sets);
+        return create(this.keys, this.groups);
     }
 }
 
@@ -197,24 +262,48 @@ export function Generator() {
     return new AtomSetGenerator();
 }
 
-function ofKeysAndSetsElemements(keys: number[], sets: IntMap<OrderedSet>): AtomSetElements {
+/** When adding groups, compare them to existing ones. If they all match, return the whole original set. */
+class ChildGenerator {
+    private keys: number[] = [];
+    private groups = IntMap.Mutable<AtomGroup>();
+    private aEqual = 0;
+    private bEqual = 0;
+
+    add(unit: number, group: AtomGroup, a: AtomGroup, b: AtomGroup) {
+        if (AtomGroup.size(group) === 0) return;
+        if (a === group) this.aEqual++;
+        if (b === group) this.bEqual++;
+        this.keys[this.keys.length] = unit;
+        this.groups.set(unit, group);
+    }
+
+    addA(unit: number, group: AtomGroup, a: AtomGroup) {
+        if (AtomGroup.size(group) === 0) return;
+
+        if (a === group) this.aEqual++;
+        this.keys[this.keys.length] = unit;
+        this.groups.set(unit, group);
+    }
+
+    constructor(private a: AtomSetImpl, private b: AtomSetImpl) {
+    }
+
+    getSet(): AtomSetImpl {
+        if (this.aEqual === this.a.keys.length) return this.a;
+        if (this.bEqual === this.b.keys.length) return this.b;
+        return create(this.keys, this.groups);
+    }
+}
+
+function create(keys: number[], groups: IntMap<AtomGroup>): AtomSetImpl {
     sortArray(keys);
     let runningSize = 0;
     const offsets = new Int32Array(keys.length + 1);
     for (let i = 0, _i = keys.length; i < _i; i++) {
-        runningSize += OrderedSet.size(sets.get(keys[i]));
+        runningSize += AtomGroup.size(groups.get(keys[i]));
         offsets[i + 1] = runningSize;
     }
-    return { keys: SortedArray.ofSortedArray(keys), sets: IntMap.asImmutable(sets), offsets, hashCode: -1 };
-}
-
-
-function ofKeysAndSets(keys: number[], sets: IntMap<OrderedSet>) {
-    if (keys.length === 1) {
-        const set = sets.get(keys[0]);
-        if (OrderedSet.size(set) === 1) return Atom.create(keys[0], OrderedSet.getAt(set, 0));
-    }
-    return ofKeysAndSetsElemements(keys, sets);
+    return { keys: SortedArray.ofSortedArray(keys), groups: IntMap.asImmutable(groups), offsets, hashCode: -1 };
 }
 
 function getUniqueElements(xs: number[]): number[] {
@@ -239,7 +328,7 @@ function normalizeArray(xs: number[]): number[] {
     return xs;
 }
 
-function ofAtoms(xs: ArrayLike<Atom>) {
+function ofAtomsImpl(xs: ArrayLike<Atom>, template: AtomSetImpl) {
     if (xs.length === 0) return Empty;
 
     const elements = IntMap.Mutable<number[]>();
@@ -256,13 +345,14 @@ function ofAtoms(xs: ArrayLike<Atom>) {
         }
     }
 
-    const sets = IntMap.Mutable<OrderedSet>();
+    const generator = TemplateGenerator(template);
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        sets.set(k, OrderedSet.ofSortedArray(normalizeArray(elements.get(k))));
+        const group = AtomGroup.createNew(OS.ofSortedArray(normalizeArray(elements.get(k))));
+        generator.add(k, group);
     }
 
-    return ofKeysAndSets(keys, sets);
+    return generator.getSet();
 }
 
 function getOffsetIndex(xs: ArrayLike<number>, value: number) {
@@ -280,32 +370,13 @@ function getOffsetIndex(xs: ArrayLike<number>, value: number) {
     return value < xs[min] ? min - 1 : min;
 }
 
-function getAtE(set: AtomSetElements, i: number): Atom {
-    const { offsets, keys } = set;
-    const o = getOffsetIndex(offsets, i);
-    if (o >= offsets.length - 1) return 0 as any;
-    const k = keys[o];
-    const e = OrderedSet.getAt(set.sets.get(k), i - offsets[o]);
-    return Atom.create(k, e);
-}
-
-function indexOfE(set: AtomSetElements, t: Atom) {
-    const { keys } = set;
-    const u = Atom.unit(t);
-    const setIdx = SortedArray.indexOf(keys, u);
-    if (setIdx < 0) return -1;
-    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, sets } = set;
+function computeHash(set: AtomSetImpl) {
+    const { keys, groups } = 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(sets.get(k))) | 0;
+        hash = (31 * hash + AtomGroup.hashCode(groups.get(k))) | 0;
     }
     hash = (31 * hash + size(set)) | 0;
     hash = hash1(hash);
@@ -313,177 +384,43 @@ function computeHash(set: AtomSetElements) {
     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 (!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(aSets.get(k), bSets.get(k))) return false;
-    }
-    return true;
-}
-
-function areIntersectingNE(a: Atom, b: AtomSetElements) {
-    const u = Atom.unit(a);
-    return b.sets.has(u) && OrderedSet.has(b.sets.get(u), Atom.index(a));
-}
-
-function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return true;
-    const keysA = a.keys, keysB = b.keys;
-    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 = aSets.get(k), bk = bSets.get(k);
-        if (!!ak && !!bk && OrderedSet.areIntersecting(ak, bk)) return true;
-    }
-    return false;
-}
-
-function intersectNE(a: Atom, b: AtomSetElements) {
-    const u = Atom.unit(a);
-    return b.sets.has(u) && OrderedSet.has(b.sets.get(u), Atom.index(a)) ? a : Empty;
-}
-
-function intersectEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return a;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty;
-    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;
-    const generator = Generator();
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const bk = bSets.get(k);
-        if (!bk) continue;
-        generator.add(k, OrderedSet.intersect(aSets.get(k), bk));
-    }
-    return generator.getSet();
-}
-
-function subtractNE(a: Atom, b: AtomSetElements) {
-    return hasAtom(b, a) ? Empty : a;
-}
-
-function subtractEN(a: AtomSetElements, b: Atom): AtomSetImpl {
-    if (!hasAtom(a, b)) return a;
-
-    const u = Atom.unit(b), v = Atom.index(b);
-    const { sets: aSets } = a;
-    const set = aSets.get(u);
-
-    if (OrderedSet.size(set) === 1) {
-        // remove the entire unit.
-        const generator = Generator();
-        for (let i = 0, _i = a.keys.length; i < _i; i++) {
-            const k = a.keys[i];
-            if (k !== u) generator.add(k, aSets.get(k))
-        }
-        return generator.getSet();
-    } else {
-        const generator = Generator();
-        for (let i = 0, _i = a.keys.length; i < _i; i++) {
-            const k = a.keys[i];
-            if (k === u) generator.add(k, OrderedSet.subtract(set, OrderedSet.ofSingleton(v)))
-            else generator.add(k, aSets.get(k))
-        }
-        return generator.getSet();
-    }
-}
-
-function subtractEE(a: AtomSetElements, b: AtomSetElements) {
-    if (a === b) return Empty;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(keysA, keysB)) return a;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-
-    const generator = Generator();
-    const { sets: aSets } = a;
-    const { sets: bSets } = b;
-    for (let i = 0; i < start; i++) {
-        const k = keysA[i];
-        generator.add(k, aSets.get(k));
-    }
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const ak = aSets.get(k), bk = bSets.get(k);
-        if (!!bk) {
-            const subtraction = OrderedSet.subtract(ak, bk);
-            generator.add(k, subtraction);
-        } else {
-            generator.add(k, ak);
-        }
-    }
-    for (let i = end, _i = keysA.length; i < _i; i++) {
-        const k = keysA[i];
-        generator.add(k, aSets.get(k));
-    }
-    return generator.getSet();
-}
-
-function findUnion(sets: ArrayLike<AtomSetImpl>) {
+function findUnion(sets: ArrayLike<AtomSetImpl>, template: 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];
+    if (sets.length === 2 && sets[0] === sets[1]) return sets[0];
 
-    const eCount = { count: 0 };
-    const ns = unionN(sets, eCount);
-    if (!eCount.count) return ns;
-    const generator = Generator();
+    const keys: number[] = [];
+    const groups = IntMap.Mutable<AtomGroup>();
     for (let i = 0, _i = sets.length; i < _i; i++) {
-        const s = sets[i];
-        if (typeof s !== 'number') unionInto(generator, s as AtomSetElements);
+        unionInto(keys, groups, sets[i]);
     }
-    if (size(ns as AtomSetImpl) > 0) {
-        if (typeof ns === 'number') unionIntoN(generator, ns as any);
-        else unionInto(generator, ns as AtomSetElements);
-    }
-    return generator.getSet();
-}
 
-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 ofAtoms(sets as ArrayLike<Atom>);
-    const packed = Atom.createEmptyArray(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 ofAtoms(packed as any);
+    return normalizeUnion(keys, groups, template);
 }
 
-function unionInto(builder: AtomSetGenerator, a: AtomSetElements) {
-    const keys = a.keys;
-    const { sets: aSets } = a;
+function normalizeUnion(keys: number[], groups: IntMap.Mutable<AtomGroup>, template: AtomSetImpl) {
+    let equalCount = 0;
+    let tg = template.groups, a: AtomGroup, t: AtomGroup;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        builder.addUnion(k, aSets.get(k));
+        if (tg.has(k) && AtomGroup.areEqual(a = groups.get(k), t = tg.get(k))) {
+            groups.set(k, t);
+            equalCount++;
+        }
     }
+    return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups);
 }
 
-function unionIntoN(builder: AtomSetGenerator, a: Atom) {
-    const u = Atom.unit(a);
-    builder.addUnion(u, OrderedSet.ofSingleton(Atom.index(a)));
+function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSetImpl) {
+    const setKeys = a.keys;
+    const { groups: aG } = a;
+    for (let i = 0, _i = setKeys.length; i < _i; i++) {
+        const k = setKeys[i];
+        if (groups.has(k)) {
+            groups.set(k, AtomGroup.union(aG.get(k), groups.get(k)))
+        } else {
+            keys[keys.length] = k;
+            groups.set(k, aG.get(k));
+        }
+    }
 }

+ 0 - 57
src/mol-model/structure/structure/atom/set.1.ts

@@ -1,57 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { SortedArray, Iterator } from 'mol-data/int'
-import Atom from '../atom'
-import AtomGroup from './group'
-import * as Impl from './impl/set.1'
-import * as Builders from './impl/builder'
-
-/** A map-like representation of grouped atom set */
-namespace AtomSet {
-    export const Empty: AtomSet = Impl.Empty as any;
-
-    export const ofAtoms: (atoms: ArrayLike<Atom>, template: AtomSet) => AtomSet = Impl.ofAtoms as any;
-
-    export const unitCount: (set: AtomSet) => number = Impl.keyCount as any;
-    export const unitIds: (set: AtomSet) => SortedArray = Impl.getKeys as any;
-    export const unitHas: (set: AtomSet, id: number) => boolean = Impl.hasKey as any;
-    export const unitGetId: (set: AtomSet, i: number) => number = Impl.getKey as any;
-
-    export const unitGetById: (set: AtomSet, key: number) => AtomGroup = Impl.getByKey as any;
-    export const unitGetByIndex: (set: AtomSet, i: number) => AtomGroup = Impl.getByIndex as any;
-
-    export const atomCount: (set: AtomSet) => number = Impl.size as any;
-    export const atomHas: (set: AtomSet, x: Atom) => boolean = Impl.hasAtom as any;
-    export const atomIndexOf: (set: AtomSet, x: Atom) => number = Impl.indexOf as any;
-    export const atomGetAt: (set: AtomSet, i: number) => Atom = Impl.getAt as any;
-    export const atoms: (set: AtomSet) => Iterator<Atom> = Impl.values as any;
-
-    export const hashCode: (set: AtomSet) => number = Impl.hashCode as any;
-    export const areEqual: (a: AtomSet, b: AtomSet) => boolean = Impl.areEqual as any;
-    export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = Impl.areIntersecting as any;
-
-    export const union: (sets: AtomSet[], template: AtomSet) => AtomSet = Impl.unionMany as any;
-    export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Impl.intersect as any;
-    export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Impl.subtract as any;
-
-    export type Builder = Builders.Builder
-    export const LinearBuilder = Builders.LinearBuilder
-    export const UnsortedBuilder = Builders.UnsortedBuilder
-
-    export interface Generator { add(unit: number, set: AtomGroup): void, getSet(): AtomSet }
-    export const Generator: () => Generator = Impl.Generator as any
-    export const TemplateGenerator: (template: AtomSet) => Generator = Impl.TemplateGenerator as any
-
-    // TODO: bounding sphere
-    // TODO: distance, areWithIn?
-    // TODO: check connected
-    // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?
-}
-
-interface AtomSet { '@type': 'atom-set' | Atom['@type'] }
-
-export default AtomSet

+ 8 - 7
src/mol-model/structure/structure/atom/set.ts

@@ -4,8 +4,9 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { OrderedSet, SortedArray, Iterator } from 'mol-data/int'
+import { SortedArray, Iterator } from 'mol-data/int'
 import Atom from '../atom'
+import AtomGroup from './group'
 import * as Impl from './impl/set'
 import * as Builders from './impl/builder'
 
@@ -13,15 +14,15 @@ import * as Builders from './impl/builder'
 namespace AtomSet {
     export const Empty: AtomSet = Impl.Empty as any;
 
-    export const create: (data: Atom | ArrayLike<Atom>) => AtomSet = Impl.create as any;
+    export const ofAtoms: (atoms: ArrayLike<Atom>, template: AtomSet) => AtomSet = Impl.ofAtoms as any;
 
     export const unitCount: (set: AtomSet) => number = Impl.keyCount as any;
     export const unitIds: (set: AtomSet) => SortedArray = Impl.getKeys as any;
     export const unitHas: (set: AtomSet, id: number) => boolean = Impl.hasKey as any;
     export const unitGetId: (set: AtomSet, i: number) => number = Impl.getKey as any;
 
-    export const unitGetById: (set: AtomSet, key: number) => OrderedSet = Impl.getByKey as any;
-    export const unitGetByIndex: (set: AtomSet, i: number) => OrderedSet = Impl.getByIndex as any;
+    export const unitGetById: (set: AtomSet, key: number) => AtomGroup = Impl.getByKey as any;
+    export const unitGetByIndex: (set: AtomSet, i: number) => AtomGroup = Impl.getByIndex as any;
 
     export const atomCount: (set: AtomSet) => number = Impl.size as any;
     export const atomHas: (set: AtomSet, x: Atom) => boolean = Impl.hasAtom as any;
@@ -33,8 +34,7 @@ namespace AtomSet {
     export const areEqual: (a: AtomSet, b: AtomSet) => boolean = Impl.areEqual as any;
     export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = Impl.areIntersecting as any;
 
-    export const union: (a: AtomSet, b: AtomSet) => AtomSet = Impl.union as any;
-    export const unionMany: (sets: AtomSet[]) => AtomSet = Impl.unionMany as any;
+    export const union: (sets: AtomSet[], template: AtomSet) => AtomSet = Impl.unionMany as any;
     export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Impl.intersect as any;
     export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Impl.subtract as any;
 
@@ -42,8 +42,9 @@ namespace AtomSet {
     export const LinearBuilder = Builders.LinearBuilder
     export const UnsortedBuilder = Builders.UnsortedBuilder
 
-    export interface Generator { add(unit: number, set: OrderedSet): void, getSet(): AtomSet }
+    export interface Generator { add(unit: number, set: AtomGroup): void, getSet(): AtomSet }
     export const Generator: () => Generator = Impl.Generator as any
+    export const TemplateGenerator: (template: AtomSet) => Generator = Impl.TemplateGenerator as any
 
     // TODO: bounding sphere
     // TODO: distance, areWithIn?

+ 5 - 5
src/mol-model/structure/structure/structure.ts

@@ -38,16 +38,16 @@ namespace Structure {
         for (let c = 0; c < chains.count; c++) {
             const group = AtomGroup.createNew(OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
             const unit = Unit.create(model, SymmetryOperator.Default, group);
-            builder.add(unit, OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
+            builder.add(unit, unit.naturalGroup);
         }
 
         return builder.getStructure();
     }
 
     export interface Builder {
-        add(unit: Unit, atoms: OrderedSet): void,
+        add(unit: Unit, atoms: AtomGroup): void,
         addUnit(unit: Unit): void,
-        setAtoms(unitId: number, atoms: OrderedSet): void,
+        setAtoms(unitId: number, atoms: AtomGroup): void,
         getStructure(): Structure,
         readonly atomCount: number
     }
@@ -58,9 +58,9 @@ namespace Structure {
         private atoms = AtomSet.Generator();
         atomCount = 0;
 
-        add(unit: Unit, atoms: OrderedSet) { const id = this.addUnit(unit); this.setAtoms(id, atoms); }
+        add(unit: Unit, atoms: AtomGroup) { const id = this.addUnit(unit); this.setAtoms(id, atoms); }
         addUnit(unit: Unit) { const id = this._unitId++; this.units[id] = unit; return id; }
-        setAtoms(unitId: number, atoms: OrderedSet) { this.atoms.add(unitId, atoms); this.atomCount += OrderedSet.size(atoms); }
+        setAtoms(unitId: number, atoms: AtomGroup) { this.atoms.add(unitId, atoms); this.atomCount += AtomGroup.size(atoms); }
         getStructure(): Structure { return this.atomCount > 0 ? Structure.create(this.units, this.atoms.getSet()) : Empty; }
     }
 

+ 78 - 78
src/perf-tests/sets.ts

@@ -2,84 +2,84 @@ import * as B from 'benchmark'
 import { Tuple, Segmentation, OrderedSet as OrdSet } from 'mol-data/int'
 import { AtomSet } from 'mol-model/structure'
 
-export namespace Iteration {
-    const U = 1000, V = 2500;
-
-    const control: number[] = [];
-    const sets = Object.create(null);
-    for (let i = 1; i < U; i++) {
-        const set = [];
-        for (let j = 1; j < V; j++) {
-            control[control.length] = j * j + 1;
-            set[set.length] = j * j + 1;
-        }
-        sets[i * i] = OrdSet.ofSortedArray(set);
-    }
-    const ms = AtomSet.create(sets);
-
-    export function native() {
-        let s = 0;
-        for (let i = 0, _i = control.length; i < _i; i++) s += control[i];
-        return s;
-    }
-
-    export function iterators() {
-        let s = 0;
-        const it = AtomSet.atoms(ms);
-        while (it.hasNext) {
-            const v = it.move();
-            s += Tuple.snd(v);
-        }
-        return s;
-    }
-
-    export function elementAt() {
-        let s = 0;
-        for (let i = 0, _i = AtomSet.atomCount(ms); i < _i; i++) s += Tuple.snd(AtomSet.atomGetAt(ms, i));
-        return s;
-    }
-
-    export function manual() {
-        let s = 0;
-        const keys = AtomSet.unitIds(ms);
-        for (let i = 0, _i = OrdSet.size(keys); i < _i; i++) {
-            const set = AtomSet.unitGetById(ms, OrdSet.getAt(keys, i));
-            for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
-                s += OrdSet.getAt(set, j);
-            }
-        }
-        return s;
-    }
-
-    export function manual1() {
-        let s = 0;
-        for (let i = 0, _i = AtomSet.unitCount(ms); i < _i; i++) {
-            const set = AtomSet.unitGetByIndex(ms, i);
-            for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
-                s += OrdSet.getAt(set, j);
-            }
-        }
-        return s;
-    }
-
-    export function run() {
-        const suite = new B.Suite();
-
-        // const values: number[] = [];
-        // for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0;
-
-        console.log(Iteration.native(), Iteration.iterators(), Iteration.elementAt(), Iteration.manual(), Iteration.manual1());
-
-        suite
-            .add('native', () => Iteration.native())
-            .add('iterators', () => Iteration.iterators())
-            .add('manual', () => Iteration.manual())
-            .add('manual1', () => Iteration.manual1())
-            .add('el at', () => Iteration.elementAt())
-            .on('cycle', (e: any) => console.log(String(e.target)))
-            .run();
-    }
-}
+// export namespace Iteration {
+//     const U = 1000, V = 2500;
+
+//     const control: number[] = [];
+//     const sets = Object.create(null);
+//     for (let i = 1; i < U; i++) {
+//         const set = [];
+//         for (let j = 1; j < V; j++) {
+//             control[control.length] = j * j + 1;
+//             set[set.length] = j * j + 1;
+//         }
+//         sets[i * i] = OrdSet.ofSortedArray(set);
+//     }
+//     const ms = AtomSet.create(sets);
+
+//     export function native() {
+//         let s = 0;
+//         for (let i = 0, _i = control.length; i < _i; i++) s += control[i];
+//         return s;
+//     }
+
+//     export function iterators() {
+//         let s = 0;
+//         const it = AtomSet.atoms(ms);
+//         while (it.hasNext) {
+//             const v = it.move();
+//             s += Tuple.snd(v);
+//         }
+//         return s;
+//     }
+
+//     export function elementAt() {
+//         let s = 0;
+//         for (let i = 0, _i = AtomSet.atomCount(ms); i < _i; i++) s += Tuple.snd(AtomSet.atomGetAt(ms, i));
+//         return s;
+//     }
+
+//     export function manual() {
+//         let s = 0;
+//         const keys = AtomSet.unitIds(ms);
+//         for (let i = 0, _i = OrdSet.size(keys); i < _i; i++) {
+//             const set = AtomSet.unitGetById(ms, OrdSet.getAt(keys, i));
+//             for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
+//                 s += OrdSet.getAt(set, j);
+//             }
+//         }
+//         return s;
+//     }
+
+//     export function manual1() {
+//         let s = 0;
+//         for (let i = 0, _i = AtomSet.unitCount(ms); i < _i; i++) {
+//             const set = AtomSet.unitGetByIndex(ms, i);
+//             for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
+//                 s += OrdSet.getAt(set, j);
+//             }
+//         }
+//         return s;
+//     }
+
+//     export function run() {
+//         const suite = new B.Suite();
+
+//         // const values: number[] = [];
+//         // for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0;
+
+//         console.log(Iteration.native(), Iteration.iterators(), Iteration.elementAt(), Iteration.manual(), Iteration.manual1());
+
+//         suite
+//             .add('native', () => Iteration.native())
+//             .add('iterators', () => Iteration.iterators())
+//             .add('manual', () => Iteration.manual())
+//             .add('manual1', () => Iteration.manual1())
+//             .add('el at', () => Iteration.elementAt())
+//             .on('cycle', (e: any) => console.log(String(e.target)))
+//             .run();
+//     }
+// }
 
 export namespace Union {
     function createArray(n: number) {

+ 11 - 11
src/perf-tests/structure.ts

@@ -10,8 +10,8 @@ import * as util from 'util'
 import * as fs from 'fs'
 import CIF from 'mol-io/reader/cif'
 
-import { Structure, Model, Queries as Q, Atom, AtomSet, Selection, Symmetry } from 'mol-model/structure'
-import { OrderedSet as OrdSet, Segmentation } from 'mol-data/int'
+import { Structure, Model, Queries as Q, Atom, AtomGroup, AtomSet, Selection, Symmetry } from 'mol-model/structure'
+import { Segmentation } from 'mol-data/int'
 
 import to_mmCIF from 'mol-model/structure/export/mmcif'
 
@@ -78,8 +78,8 @@ export namespace PropertyAccess {
             const set = AtomSet.unitGetByIndex(atoms, i);
 
 
-            for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
-                l.atom = OrdSet.getAt(set, j);
+            for (let j = 0, _j = AtomGroup.size(set); j < _j; j++) {
+                l.atom = AtomGroup.getAt(set, j);
                 s += p(l);
             }
         }
@@ -100,20 +100,20 @@ export namespace PropertyAccess {
             l.unit = unit;
             const set = AtomSet.unitGetByIndex(atoms, i);
 
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
+            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.atoms);
             const residues = unit.hierarchy.residueSegments;
             while (chainsIt.hasNext) {
                 cC++;
 
                 const chainSegment = chainsIt.move();
-                const residuesIt = Segmentation.transientSegments(residues, set, chainSegment);
+                const residuesIt = Segmentation.transientSegments(residues, set.atoms, chainSegment);
                 while (residuesIt.hasNext) {
                     rC++;
                     const residueSegment = residuesIt.move();
                     // l.atom = OrdSet.getAt(set, residueSegment.start);
                     // console.log(unit.hierarchy.residues.auth_comp_id.value(unit.residueIndex[l.atom]), l.atom, OrdSet.getAt(set, residueSegment.end))
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.atom = OrdSet.getAt(set, j);
+                        l.atom = AtomGroup.getAt(set, j);
                         vA++;
                         s += p(l);
                     }
@@ -137,9 +137,9 @@ export namespace PropertyAccess {
             const unit = units[unitIds[i]];
             l.unit = unit;
             const set = AtomSet.unitGetByIndex(atoms, i);
-            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);
+            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set.atoms);
             while (residuesIt.hasNext) {
-                l.atom = OrdSet.getAt(set, residuesIt.move().start);
+                l.atom = AtomGroup.getAt(set, residuesIt.move().start);
                 s += p(l);
             }
         }
@@ -204,8 +204,8 @@ export namespace PropertyAccess {
             const set = AtomSet.unitGetByIndex(atoms, i);
             //const { residueIndex, chainIndex } = unit;
             const p = unit.conformation.atomId.value;
-            for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
-                const aI = OrdSet.getAt(set, j);
+            for (let j = 0, _j = AtomGroup.size(set); j < _j; j++) {
+                const aI = AtomGroup.getAt(set, j);
                 s += p(aI);
             }
         }