Browse Source

IntraUnitBonds and other stuff

David Sehnal 7 years ago
parent
commit
efa5ef1bd5

+ 18 - 18
src/apps/structure-info/model.ts

@@ -41,29 +41,28 @@ export function atomLabel(model: Model, aI: number) {
 
 
 export function printBonds(structure: Structure) {
-    // TODO
-    // for (const unit of structure.units) {
-    //     const unit = units[OrderedSet.getAt(unitIds, i)];
-    //     const group = ElementSet.groupFromUnitIndex(elements, OrderedSet.getAt(unitIds, i));
+    for (const unit of structure.units) {
+        if (!Unit.isAtomic(unit)) continue;
 
-    //     const { count, offset, neighbor } = Unit.getGroupBonds(unit, group);
-    //     const { model }  = unit;
+        const elements = unit.elements;
+        const { count, offset, neighbor } = unit.bonds;
+        const { model }  = unit;
 
-    //     if (!count) continue;
+        if (!count) continue;
 
-    //     for (let j = 0; j < offset.length - 1; ++j) {
-    //         const start = offset[j];
-    //         const end = offset[j + 1];
+        for (let j = 0; j < offset.length - 1; ++j) {
+            const start = offset[j];
+            const end = offset[j + 1];
 
-    //         if (end <= start) continue;
+            if (end <= start) continue;
 
-    //         const aI = ElementGroup.getAt(group, j);
-    //         for (let _bI = start; _bI < end; _bI++) {
-    //             const bI = ElementGroup.getAt(group, neighbor[_bI])
-    //             console.log(`${atomLabel(model, aI)} -- ${atomLabel(model, bI)}`);
-    //         }
-    //     }
-    // }
+            const aI = elements[j];
+            for (let _bI = start; _bI < end; _bI++) {
+                const bI = elements[neighbor[_bI]];
+                console.log(`${atomLabel(model, aI)} -- ${atomLabel(model, bI)}`);
+            }
+        }
+    }
 }
 
 export function printSequence(model: Model) {
@@ -123,6 +122,7 @@ async function run(mmcif: mmCIF_Database) {
     printSequence(models[0]);
     printIHMModels(models[0]);
     printUnits(structure);
+    printBonds(structure);
 }
 
 async function runDL(pdb: string) {

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

@@ -1,202 +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 ElementSet from '../structure/element/set'
-import Element from '../structure/element'
-import ElementGroup from '../structure/element/group'
-
-describe('atom set', () => {
-    const p = (i: number, j: number) => Element.create(i, j);
-
-    function setToPairs(set: ElementSet): ArrayLike<Element> {
-        const ret: Element[] = [];
-        const it = ElementSet.elements(set);
-        while (it.hasNext) {
-            ret[ret.length] = it.move();
-        }
-        return ret;
-    }
-
-    it('singleton pair', () => {
-        const set = ElementSet.ofAtoms([p(10, 11)], ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(true);
-        expect(ElementSet.elementHas(set, p(11, 11))).toBe(false);
-        expect(ElementSet.elementAt(set, 0)).toBe(p(10, 11));
-        expect(ElementSet.elementCount(set)).toBe(1);
-    });
-
-    it('singleton atom', () => {
-        const set = ElementSet.singleton(p(10, 11), ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(true);
-        expect(ElementSet.elementHas(set, p(11, 11))).toBe(false);
-        expect(ElementSet.elementAt(set, 0)).toBe(p(10, 11));
-        expect(ElementSet.elementCount(set)).toBe(1);
-    });
-
-    it('multi', () => {
-        const gen = ElementSet.Generator();
-        gen.add(1, ElementGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7])));
-        gen.add(3, ElementGroup.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(ElementSet.elementCount(set)).toBe(ret.length);
-        expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(false);
-        expect(ElementSet.elementHas(set, p(3, 0))).toBe(true);
-        expect(ElementSet.elementHas(set, p(1, 7))).toBe(true);
-        for (let i = 0; i < ElementSet.elementCount(set); i++) {
-            expect(Element.areEqual(ElementSet.elementAt(set, i), ret[i])).toBe(true);
-        }
-    });
-
-    it('template', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-        const gen = ElementSet.TemplateGenerator(template);
-        gen.add(0, OrderedSet.ofSortedArray([1, 2, 6]));
-        gen.add(1, OrderedSet.ofSingleton(3));
-        const set = gen.getSet();
-
-        expect(ElementSet.groupFromUnitIndex(set, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(set, 1)).toBe(ElementSet.groupFromUnitIndex(template, 1));
-        expect(set).toBe(template);
-    });
-
-    it('template 1', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-        const gen = ElementSet.TemplateGenerator(template);
-        gen.add(0, OrderedSet.ofSortedArray([1, 2, 6]));
-        gen.add(1, OrderedSet.ofSingleton(4));
-        const set = gen.getSet();
-
-        expect(ElementSet.groupFromUnitIndex(set, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(set, 1) === ElementSet.groupFromUnitIndex(template, 1)).toBe(false);
-        expect(set === template).toBe(false);
-    });
-
-    it('template union', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-
-        const p13 = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const p01 = ElementSet.ofAtoms([p(0, 1)], ElementSet.Empty);
-        const p02 = ElementSet.ofAtoms([p(0, 2)], ElementSet.Empty);
-        const p06 = ElementSet.ofAtoms([p(0, 6)], ElementSet.Empty);
-
-        const u0 = ElementSet.union([p01, p02, p06], template);
-        const u1 = ElementSet.union([p01, p02, p06, p13], template);
-        expect(ElementSet.groupFromUnitIndex(u0, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(u1, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(u1, 1)).toBe(ElementSet.groupFromUnitIndex(template, 1));
-        expect(u1).toBe(template);
-    });
-
-    it('element at / index of', () => {
-        const control: Element[] = [];
-        const gen = ElementSet.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, ElementGroup.createNew(OrderedSet.ofSortedArray(set)));
-        }
-        const ms = gen.getSet();
-        for (let i = 0; i < control.length; i++) {
-            expect(Element.areEqual(ElementSet.elementAt(ms, i), control[i])).toBe(true);
-        }
-
-        for (let i = 0; i < control.length; i++) {
-            expect(ElementSet.elementIndexOf(ms, control[i])).toBe(i);
-        }
-    });
-
-    it('packed pairs', () => {
-        const set = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
-    });
-
-    it('equality', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const f = ElementSet.ofAtoms([p(3, 3)], ElementSet.Empty);
-
-        expect(ElementSet.areEqual(a, a)).toBe(true);
-        expect(ElementSet.areEqual(a, b)).toBe(true);
-        expect(ElementSet.areEqual(a, c)).toBe(false);
-        expect(ElementSet.areEqual(a, d)).toBe(false);
-        expect(ElementSet.areEqual(d, d)).toBe(true);
-        expect(ElementSet.areEqual(d, e)).toBe(true);
-        expect(ElementSet.areEqual(d, f)).toBe(false);
-    });
-
-    it('are intersecting', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const f = ElementSet.ofAtoms([p(3, 3)], ElementSet.Empty);
-        const g = ElementSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], ElementSet.Empty);
-
-        expect(ElementSet.areIntersecting(a, a)).toBe(true);
-        expect(ElementSet.areIntersecting(a, b)).toBe(true);
-        expect(ElementSet.areIntersecting(a, c)).toBe(true);
-        expect(ElementSet.areIntersecting(a, d)).toBe(true);
-        expect(ElementSet.areIntersecting(a, g)).toBe(false);
-        expect(ElementSet.areIntersecting(d, d)).toBe(true);
-        expect(ElementSet.areIntersecting(d, e)).toBe(true);
-        expect(ElementSet.areIntersecting(d, f)).toBe(false);
-    });
-
-    it('intersection', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        expect(ElementSet.intersect(a, a)).toBe(a);
-        expect(setToPairs(ElementSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]);
-        expect(setToPairs(ElementSet.intersect(a, c))).toEqual([p(1, 3)]);
-        expect(setToPairs(ElementSet.intersect(c, d))).toEqual([]);
-    });
-
-    it('subtract', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const a1 = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(0, 2)], ElementSet.Empty);
-        expect(setToPairs(ElementSet.subtract(a, a))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(a, a1))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]);
-        expect(setToPairs(ElementSet.subtract(c, d))).toEqual([p(1, 3)]);
-        expect(setToPairs(ElementSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]);
-        expect(setToPairs(ElementSet.subtract(c, a))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(d, a))).toEqual([p(2, 3)]);
-        expect(setToPairs(ElementSet.subtract(a, e))).toEqual([p(0, 1), p(0, 6), p(1, 3)]);
-    });
-
-    it('union', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1)], ElementSet.Empty);
-        const a1 = ElementSet.ofAtoms([p(1, 3), p(0, 1)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        expect(ElementSet.union([a], ElementSet.Empty)).toBe(a);
-        expect(ElementSet.union([a, a], ElementSet.Empty)).toBe(a);
-        expect(setToPairs(ElementSet.union([a, a], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(ElementSet.union([a, a1], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(ElementSet.union([a, b], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
-        expect(setToPairs(ElementSet.union([c, d], ElementSet.Empty))).toEqual([p(1, 3), p(2, 3)]);
-        expect(setToPairs(ElementSet.union([a, b, c, d], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
-    });
-});

+ 5 - 5
src/mol-model/structure/model/formats/mmcif/bonds.ts

@@ -9,9 +9,9 @@ import Model from '../../model'
 import { BondType } from '../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
 import { Column } from 'mol-data/db'
-import { GroupBonds } from '../../../structure/element/properties/bonds/group-data';
+import { IntraUnitBonds } from '../../../structure/unit/bonds';
 
-export class StructConn implements GroupBonds.StructConn {
+export class StructConn implements IntraUnitBonds.StructConn {
     private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
     private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
 
@@ -71,7 +71,7 @@ export class StructConn implements GroupBonds.StructConn {
 }
 
 export namespace StructConn {
-    export interface Entry extends GroupBonds.StructConnEntry {
+    export interface Entry extends IntraUnitBonds.StructConnEntry {
         distance: number,
         order: number,
         flags: number,
@@ -181,7 +181,7 @@ export namespace StructConn {
     }
 }
 
-export class ComponentBondInfo implements GroupBonds.ComponentBondInfo {
+export class ComponentBondInfo implements IntraUnitBonds.ComponentBondInfo {
     entries: Map<string, ComponentBondInfo.Entry> = new Map();
 
     newEntry(id: string) {
@@ -192,7 +192,7 @@ export class ComponentBondInfo implements GroupBonds.ComponentBondInfo {
 }
 
 export namespace ComponentBondInfo {
-    export class Entry implements GroupBonds.ComponentBondInfoEntry {
+    export class Entry implements IntraUnitBonds.ComponentBondInfoEntry {
         map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
 
         add(a: string, b: string, order: number, flags: number, swap = true) {

+ 3 - 2
src/mol-model/structure/query/generators.ts

@@ -136,9 +136,10 @@ class LinearGroupingBuilder {
 
     private singletonSelection(): Selection {
         const builder = this.source.subsetBuilder(true);
+        const loc = Element.Location();
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            const e = this.builders[i].singleton();
-            builder.addToUnit(Element.unit(e), Element.index(e));
+            this.builders[i].setSingletonLocation(loc);
+            builder.addToUnit(loc.unit.id, loc.element);
         }
         return Selection.Singletons(this.source, builder.getStructure());
     }

+ 3 - 35
src/mol-model/structure/query/selection.ts

@@ -6,7 +6,7 @@
 
 import { HashSet } from 'mol-data/generic'
 import { Structure } from '../structure'
-import { SortedArray } from 'mol-data/int';
+import { StructureUtils } from './utils';
 
 // A selection is a pair of a Structure and a sequence of unique AtomSets
 type Selection = Selection.Singletons | Selection.Sequence
@@ -31,7 +31,7 @@ namespace Selection {
     export function unionStructure(sel: Selection): Structure {
         if (isEmpty(sel)) return Structure.Empty;
         if (isSingleton(sel)) return sel.structure;
-        return union(sel.source, sel.structures);
+        return StructureUtils.union(sel.source, sel.structures);
     }
 
     export interface Builder {
@@ -42,7 +42,7 @@ namespace Selection {
     function getSelection(source: Structure, structures: Structure[], allSingletons: boolean) {
         const len = structures.length;
         if (len === 0) return Empty(source);
-        if (allSingletons) return Singletons(source, union(source, structures));
+        if (allSingletons) return Singletons(source, StructureUtils.union(source, structures));
         return Sequence(source, structures);
     }
 
@@ -83,38 +83,6 @@ namespace Selection {
     export function UniqueBuilder(structure: Structure): Builder { return new HashBuilderImpl(structure); }
 
     // TODO: spatial lookup
-
-    function union(source: Structure, structures: Structure[]) {
-        if (structures.length === 0) return Structure.Empty;
-        if (structures.length === 1) return structures[0];
-
-        const unitMap = new Map<number, SortedArray>();
-        const fullUnits = new Set<number>();
-
-        for (const { units } of structures) {
-            for (let i = 0, _i = units.length; i < _i; i++) {
-                const u = units[i];
-                if (unitMap.has(u.id)) {
-                    // check if there is anything more to union in this particual unit.
-                    if (fullUnits.has(u.id)) continue;
-                    const merged = SortedArray.union(unitMap.get(u.id)!, u.elements);
-                    unitMap.set(u.id, merged);
-                    if (merged.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
-                } else {
-                    unitMap.set(u.id, u.elements);
-                    if (u.elements.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
-                }
-            }
-        }
-
-        const builder = source.subsetBuilder(true);
-        unitMap.forEach(buildUnion, builder);
-        return builder.getStructure();
-    }
-
-    function buildUnion(this: Structure.SubsetBuilder, elements: SortedArray, id: number) {
-        this.setUnit(id, elements);
-    }
 }
 
 export default Selection

+ 107 - 0
src/mol-model/structure/query/utils.ts

@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure, Unit } from '../structure'
+import { SortedArray } from 'mol-data/int';
+
+namespace StructureUtils {
+    export function union(source: Structure, structures: Structure[]) {
+        if (structures.length === 0) return Structure.Empty;
+        if (structures.length === 1) return structures[0];
+
+        const unitMap = new Map<number, SortedArray>();
+        const fullUnits = new Set<number>();
+
+        for (const { units } of structures) {
+            for (let i = 0, _i = units.length; i < _i; i++) {
+                const u = units[i];
+                if (unitMap.has(u.id)) {
+                    // check if there is anything more to union in this particual unit.
+                    if (fullUnits.has(u.id)) continue;
+                    const merged = SortedArray.union(unitMap.get(u.id)!, u.elements);
+                    unitMap.set(u.id, merged);
+                    if (merged.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
+                } else {
+                    unitMap.set(u.id, u.elements);
+                    if (u.elements.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
+                }
+            }
+        }
+
+        const builder = source.subsetBuilder(true);
+        unitMap.forEach(buildUnion, builder);
+        return builder.getStructure();
+    }
+
+    function buildUnion(this: Structure.SubsetBuilder, elements: SortedArray, id: number) {
+        this.setUnit(id, elements);
+    }
+
+    export function areIntersecting(sA: Structure, sB: Structure): boolean {
+        if (sA === sB) return true;
+
+        let a, b;
+        if (sA.units.length < sB.units.length) { a = sA; b = sB; }
+        else { a = sB; b = sA; }
+
+        const aU = a.units, bU = b.unitMap;
+
+        for (let i = 0, _i = aU.length; i < _i; i++) {
+            const u = aU[i];
+            if (!bU.has(u.id)) continue;
+            const v = bU.get(u.id);
+            if (SortedArray.areIntersecting(u.elements, v.elements)) return true;
+        }
+
+        return false;
+    }
+
+    export function intersect(sA: Structure, sB: Structure): Structure {
+        if (sA === sB) return sA;
+        if (!areIntersecting(sA, sB)) return Structure.Empty;
+
+        let a, b;
+        if (sA.units.length < sB.units.length) { a = sA; b = sB; }
+        else { a = sB; b = sA; }
+
+        const aU = a.units, bU = b.unitMap;
+        const units: Unit[] = [];
+
+        for (let i = 0, _i = aU.length; i < _i; i++) {
+            const u = aU[i];
+            if (!bU.has(u.id)) continue;
+            const v = bU.get(u.id);
+            if (SortedArray.areIntersecting(u.elements, v.elements)) {
+                const int = SortedArray.intersect(u.elements, v.elements);
+                units[units.length] = u.getChild(int);
+            }
+        }
+
+        return Structure.create(units);
+    }
+
+    export function subtract(a: Structure, b: Structure): Structure {
+        if (a === b) return Structure.Empty;
+        if (!areIntersecting(a, b)) return a;
+
+        const aU = a.units, bU = b.unitMap;
+        const units: Unit[] = [];
+
+        for (let i = 0, _i = aU.length; i < _i; i++) {
+            const u = aU[i];
+            if (!bU.has(u.id)) continue;
+            const v = bU.get(u.id);
+            const sub = SortedArray.intersect(u.elements, v.elements);
+            if (sub.length > 0) {
+                units[units.length] = u.getChild(sub);
+            }
+        }
+
+        return Structure.create(units);
+    }
+}
+
+export { StructureUtils }

+ 2 - 2
src/mol-model/structure/structure/element.ts

@@ -15,7 +15,7 @@ namespace Element {
     export const Zero: Element = Tuple.Zero;
     export const create: (unit: number, index: number) => Element = Tuple.create;
     export const is: (x: any) => x is Element = Tuple.is;
-    export const unit: (e: Element) => number = Tuple.fst;
+    export const unitId: (e: Element) => number = Tuple.fst;
     export const index: (e: Element) => number = Tuple.snd;
     export const areEqual: (e: Element, b: Element) => boolean = Tuple.areEqual;
     export const hashCode: (e: Element) => number = Tuple.hashCode;
@@ -29,7 +29,7 @@ namespace Element {
     export interface Predicate extends Property<boolean> { }
 
     export function updateLocation(structure: Structure, l: Location, element: Element) {
-        l.unit = structure.units[unit(element)];
+        l.unit = structure.units[unitId(element)];
         l.element = index(element);
         return l;
     }

+ 0 - 77
src/mol-model/structure/structure/element/group.ts

@@ -1,77 +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 { Lookup3D } from 'mol-math/geometry'
-import Unit from '../unit'
-import { GroupBonds } from './properties/bonds/group-data';
-
-interface ElementGroup {
-    elements: OrderedSet,
-    // Unique identifier of the group, usable as partial key for various "caches".
-    key: number,
-
-    __lookup3d__?: Lookup3D,
-    __bonds__?: GroupBonds
-}
-
-namespace ElementGroup {
-    export const Empty = createNew(OrderedSet.Empty)
-
-    export function singleton(idx: number) {
-        return createNew(OrderedSet.ofSingleton(idx));
-    }
-
-    export function createNew(elements: OrderedSet): ElementGroup {
-        return { key: nextKey(), elements };
-    }
-
-    export function create(unit: Unit, elements: OrderedSet): ElementGroup {
-        //if (OrderedSet.areEqual(elements, unit.fullGroup.elements)) return unit.fullGroup;
-        return createNew(elements);
-    }
-
-    export function createChild(parent: ElementGroup, elements: OrderedSet): ElementGroup {
-        if (OrderedSet.areEqual(elements, parent.elements)) return parent;
-        return createNew(elements);
-    }
-
-    export function size(group: ElementGroup) { return OrderedSet.size(group.elements); }
-    export function has(group: ElementGroup, element: number) { return OrderedSet.has(group.elements, element); }
-    export function getAt(group: ElementGroup, i: number) { return OrderedSet.getAt(group.elements, i); }
-    export function indexOf(group: ElementGroup, element: number) { return OrderedSet.indexOf(group.elements, element); }
-    export function hashCode(group: ElementGroup) { return OrderedSet.hashCode(group.elements); }
-    export function areEqual(a: ElementGroup, b: ElementGroup) { return OrderedSet.areEqual(a.elements, b.elements); }
-
-    export function intersect(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.intersect(a.elements, b.elements);
-        if (set === a.elements) return a;
-        if (set === b.elements) return b;
-        return createNew(set);
-    }
-
-    export function union(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.union(a.elements, b.elements);
-        if (set === a.elements) return a;
-        if (set === b.elements) return b;
-        return createNew(set);
-    }
-
-    export function subtract(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.subtract(a.elements, b.elements);
-        if (set === a.elements) return a;
-        return createNew(set);
-    }
-
-    let _id = 0;
-    function nextKey() {
-        const ret = _id;
-        _id = (_id + 1) % 0x3fffffff;
-        return ret;
-    }
-}
-
-export default ElementGroup

+ 0 - 10
src/mol-model/structure/structure/element/impl/properties.ts

@@ -1,10 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// TODO: bounding sphere
-// TODO: distance, areWithIn?
-// TODO: check connected
-// TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?

+ 0 - 65
src/mol-model/structure/structure/element/impl/set-builder.ts

@@ -1,65 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import ElementSet from '../set'
-import Element from '../../element'
-import { OrderedSet, IntMap } from 'mol-data/int'
-import { sortArray } from 'mol-data/util/sort'
-
-export class Builder {
-    private keys: number[] = [];
-    private units = IntMap.Mutable<number[]>();
-    private currentUnit: number[] = [];
-
-    elementCount = 0;
-
-    add(u: number, e: number) {
-        const unit = this.units.get(u);
-        if (!!unit) { unit[unit.length] = e; }
-        else {
-            this.units.set(u, [e]);
-            this.keys[this.keys.length] = u;
-        }
-        this.elementCount++;
-    }
-
-    beginUnit() { this.currentUnit = this.currentUnit.length > 0 ? [] : this.currentUnit; }
-    addToUnit(a: number) { this.currentUnit[this.currentUnit.length] = a; this.elementCount++; }
-    commitUnit(u: number) {
-        if (this.currentUnit.length === 0) return;
-        this.keys[this.keys.length] = u;
-        this.units.set(u, this.currentUnit);
-    }
-
-    getSet(): ElementSet {
-        const generator = ElementSet.TemplateGenerator(this.parent);
-
-        for (let i = 0, _i = this.keys.length; i < _i; i++) {
-            const k = this.keys[i];
-            const unit = this.units.get(k);
-            const l = unit.length;
-            if (!this.sorted && l > 1) sortArray(unit);
-            generator.add(k, OrderedSet.ofSortedArray(unit));
-        }
-
-        return generator.getSet();
-    }
-
-    singleton(): Element {
-        const u = this.keys[0];
-        return Element.create(u, this.units.get(u)[0]);
-    }
-
-    constructor(private parent: ElementSet, private sorted: boolean) { }
-}
-
-export function LinearBuilder(parent: ElementSet) {
-    return new Builder(parent, true);
-}
-
-export function UnsortedBuilder(parent: ElementSet) {
-    return new Builder(parent, false);
-}

+ 0 - 452
src/mol-model/structure/structure/element/impl/set.ts

@@ -1,452 +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 Element from '../../element'
-import ElementGroup from '../group'
-/** Long and painful implementation starts here */
-
-export type ElementSetImpl = {
-    groups: IntMap<ElementGroup>,
-    offsets: Int32Array,
-    hashCode: number,
-    keys: SortedArray
-}
-
-export const Empty: ElementSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
-
-export function ofElements(elements: ArrayLike<Element>, template: ElementSetImpl): ElementSetImpl {
-    return ofElementsImpl(elements, template);
-}
-
-export function singleton(element: Element, template: ElementSetImpl) {
-    return singletonImpl(element, template);
-}
-
-export function getKeys(set: ElementSetImpl): SortedArray {
-    return set.keys;
-}
-
-export function keyCount(set: ElementSetImpl): number {
-    return set.keys.length;
-}
-
-export function hasKey(set: ElementSetImpl, key: number): boolean {
-    return set.groups.has(key);
-}
-
-export function getKey(set: ElementSetImpl, index: number): number {
-    return set.keys[index];
-}
-
-export function hasAtom(set: ElementSetImpl, t: Element): boolean {
-    const os = set.groups.get(Element.unit(t));
-    return !!os && ElementGroup.has(os, Element.index(t));
-}
-
-export function getByKey(set: ElementSetImpl, key: number): ElementGroup {
-    return set.groups.get(key) || ElementGroup.Empty;
-}
-
-export function getByIndex(set: ElementSetImpl, index: number): ElementGroup {
-    const key = set.keys[index];
-    return set.groups.get(key) || ElementGroup.Empty;
-}
-
-export function getAt(set: ElementSetImpl, i: number): Element {
-    const { offsets, keys } = set;
-    const o = getOffsetIndex(offsets, i);
-    if (o >= offsets.length - 1) return Element.Zero;
-    const k = keys[o];
-    const e = ElementGroup.getAt(set.groups.get(k), i - offsets[o]);
-    return Element.create(k, e);
-}
-
-export function indexOf(set: ElementSetImpl, t: Element) {
-    const { keys } = set;
-    const u = Element.unit(t);
-    const setIdx = SortedArray.indexOf(keys, u);
-    if (setIdx < 0) return -1;
-    const o = ElementGroup.indexOf(set.groups.get(u), Element.index(t));
-    if (o < 0) return -1;
-    return set.offsets[setIdx] + o;
-}
-
-/** Number elements in the "child" sets */
-export function size(set: ElementSetImpl) {
-    return set.offsets[set.offsets.length - 1];
-}
-
-export function hashCode(set: ElementSetImpl) {
-    if (set.hashCode !== -1) return set.hashCode;
-    return computeHash(set);
-}
-
-export function areEqual(a: ElementSetImpl, b: ElementSetImpl) {
-    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 (!ElementGroup.areEqual(aG.get(k), bG.get(k))) return false;
-    }
-    return true;
-}
-
-export function areIntersecting(a: ElementSetImpl, b: ElementSetImpl) {
-    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.elements, bk.elements)) return true;
-    }
-    return false;
-}
-
-export function intersect(a: ElementSetImpl, b: ElementSetImpl) {
-    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, ElementGroup.intersect(aG.get(k), bk), ak, bk);
-    }
-    return generator.getSet();
-}
-
-export function subtract(a: ElementSetImpl, b: ElementSetImpl) {
-    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 = ElementGroup.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<ElementSetImpl>, template: ElementSetImpl) {
-    return findUnion(sets, template);
-}
-
-class ElementsIterator implements Iterator<Element> {
-    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 Element.Zero;
-        const ret = Element.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).elements;
-        this.currentIndex = 0;
-        this.currentSize = OS.size(this.currentSet);
-        return true;
-    }
-
-    constructor(private elements: ElementSetImpl) {
-        this.keyCount = elements.keys.length;
-        this.hasNext = this.keyCount > 0;
-        this.advance();
-    }
-}
-
-export function values(set: ElementSetImpl): Iterator<Element> {
-    return new ElementsIterator(set);
-}
-
-export class TemplateAtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<ElementGroup>();
-    private templateGroups: IntMap<ElementGroup>;
-    private equalGroups = 0;
-
-    add(unit: number, set: OS) {
-        if (OS.size(set) === 0) return;
-        this.keys[this.keys.length] = unit;
-        if (this.templateGroups.has(unit)) {
-            const t = this.templateGroups.get(unit);
-            if (OS.areEqual(t.elements, set)) {
-                this.groups.set(unit, t);
-                this.equalGroups++;
-            } else {
-                this.groups.set(unit, ElementGroup.createNew(set));
-            }
-        } else {
-            this.groups.set(unit, ElementGroup.createNew(set));
-        }
-    }
-
-    getSet(): ElementSetImpl {
-        if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) {
-            return this.template;
-        }
-        return create(this.keys, this.groups);
-    }
-
-    constructor(private template: ElementSetImpl) {
-        this.templateGroups = template.groups;
-    }
-}
-
-export function TemplateGenerator(template: ElementSetImpl) {
-    return new TemplateAtomSetGenerator(template);
-}
-
-export class AtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<ElementGroup>();
-
-    add(unit: number, group: ElementGroup) {
-        if (ElementGroup.size(group) === 0) return;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    getSet(): ElementSetImpl {
-        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<ElementGroup>();
-    private aEqual = 0;
-    private bEqual = 0;
-
-    add(unit: number, group: ElementGroup, a: ElementGroup, b: ElementGroup) {
-        if (ElementGroup.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: ElementGroup, a: ElementGroup) {
-        if (ElementGroup.size(group) === 0) return;
-
-        if (a === group) this.aEqual++;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    constructor(private a: ElementSetImpl, private b: ElementSetImpl) {
-    }
-
-    getSet(): ElementSetImpl {
-        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<ElementGroup>): ElementSetImpl {
-    sortArray(keys);
-    let runningSize = 0;
-    const offsets = new Int32Array(keys.length + 1);
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        runningSize += ElementGroup.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 ofElementsImpl(xs: ArrayLike<Element>, template: ElementSetImpl) {
-    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 = Element.unit(x), v = Element.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];
-        generator.add(k, OS.ofSortedArray(normalizeArray(elements.get(k))));
-    }
-
-    return generator.getSet();
-}
-
-function singletonImpl(element: Element, template: ElementSetImpl) {
-    const k = Element.unit(element), i = Element.index(element);
-    const { groups } = template;
-    const gs = IntMap.Mutable<ElementGroup>();
-    if (groups.has(k)) {
-        const g = groups.get(k);
-        if (ElementGroup.size(g) === 1 && ElementGroup.getAt(g, 0) === i) {
-            gs.set(k, g);
-            return create([k], gs);
-        }
-    }
-    gs.set(k, ElementGroup.createNew(OS.ofSingleton(i)));
-    return create([k], gs);
-}
-
-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: ElementSetImpl) {
-    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 + ElementGroup.hashCode(groups.get(k))) | 0;
-    }
-    hash = (31 * hash + size(set)) | 0;
-    hash = hash1(hash);
-    set.hashCode = hash;
-    return hash;
-}
-
-function findUnion(sets: ArrayLike<ElementSetImpl>, template: ElementSetImpl) {
-    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<ElementGroup>();
-    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<ElementGroup>, template: ElementSetImpl) {
-    let equalCount = 0;
-    let tg = template.groups, a: ElementGroup, t: ElementGroup;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        if (tg.has(k) && ElementGroup.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<ElementGroup>, a: ElementSetImpl) {
-    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, ElementGroup.union(aG.get(k), groups.get(k)))
-        } else {
-            keys[keys.length] = k;
-            groups.set(k, aG.get(k));
-        }
-    }
-}

+ 0 - 65
src/mol-model/structure/structure/element/set.ts

@@ -1,65 +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, OrderedSet } from 'mol-data/int'
-import Element from '../element'
-import ElementGroup from './group'
-import * as Impl from './impl/set'
-import * as Builders from './impl/set-builder'
-import { StructureLookup3D } from '../util/lookup3d';
-import Structure from '../structure';
-
-/**
- * A map-like representation of grouped atom set
- *
- * Essentially corresponds to the type { [unitId: number]: ElementGroup }.
- */
-namespace ElementSet {
-    export const Empty: ElementSet = Impl.Empty as any;
-
-    export const ofAtoms: (elements: ArrayLike<Element>, template: ElementSet) => ElementSet = Impl.ofElements as any;
-    export const singleton: (element: Element, template: ElementSet) => ElementSet = Impl.singleton as any;
-
-    export const unitIndices: (set: ElementSet) => SortedArray = Impl.getKeys as any;
-    export const unitHas: (set: ElementSet, index: number) => boolean = Impl.hasKey as any;
-
-    export const groupCount: (set: ElementSet) => number = Impl.keyCount as any;
-    export const groupUnitIndex: (set: ElementSet, index: number) => number = Impl.getKey as any;
-    export const groupFromUnitIndex: (set: ElementSet, unitId: number) => ElementGroup = Impl.getByKey as any;
-    export const groupAt: (set: ElementSet, index: number) => ElementGroup = Impl.getByIndex as any;
-
-    export const elementCount: (set: ElementSet) => number = Impl.size as any;
-    export const elementHas: (set: ElementSet, x: Element) => boolean = Impl.hasAtom as any;
-    export const elementIndexOf: (set: ElementSet, x: Element) => number = Impl.indexOf as any;
-    export const elementAt: (set: ElementSet, i: number) => Element = Impl.getAt as any;
-    export const elements: (set: ElementSet) => Iterator<Element> = Impl.values as any;
-
-    export const hashCode: (set: ElementSet) => number = Impl.hashCode as any;
-    export const areEqual: (a: ElementSet, b: ElementSet) => boolean = Impl.areEqual as any;
-    export const areIntersecting: (a: ElementSet, b: ElementSet) => boolean = Impl.areIntersecting as any;
-
-    export const union: (sets: ArrayLike<ElementSet>, template: ElementSet) => ElementSet = Impl.unionMany as any;
-    export const intersect: (a: ElementSet, b: ElementSet) => ElementSet = Impl.intersect as any;
-    export const subtract: (a: ElementSet, b: ElementSet) => ElementSet = 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: ElementGroup): void, getSet(): ElementSet }
-    export const Generator: () => Generator = Impl.Generator as any
-
-    export interface TemplateGenerator { add(unit: number, set: OrderedSet): void, getSet(): ElementSet }
-    export const TemplateGenerator: (template: ElementSet) => TemplateGenerator = Impl.TemplateGenerator as any
-
-    // TODO: distance, areWithIn?
-    // TODO: check connected
-    // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?
-}
-
-interface ElementSet { '@type': 'element-set' }
-
-export default ElementSet

+ 11 - 6
src/mol-model/structure/structure/structure.ts

@@ -12,9 +12,8 @@ import { sortArray, sort, arraySwap, hash1 } from 'mol-data/util';
 import Element from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
+import { computeStructureBoundary } from './util/boundary';
 
-// A structure is a pair of "units" and an element set.
-// Each unit contains the data and transformation of its corresponding elements.
 class Structure {
     readonly unitMap: IntMap<Unit>;
     readonly units: ReadonlyArray<Unit>;
@@ -48,6 +47,10 @@ class Structure {
         return new Structure.ElementLocationIterator(this);
     }
 
+    get boundary() {
+        return computeStructureBoundary(this);
+    }
+
     constructor(units: ArrayLike<Unit>) {
         const map = IntMap.Mutable<Unit>();
         let elementCount = 0;
@@ -197,8 +200,10 @@ namespace Structure {
             return create(newUnits);
         }
 
-        singleton(): Element {
-            return Element.create(this.ids[0], this.unitMap.get(this.ids[0])[0]);
+        setSingletonLocation(location: Element.Location) {
+            const id = this.ids[0];
+            location.unit = this.parent.unitMap.get(id);
+            location.element = this.unitMap.get(id)[0];
         }
 
         get isEmpty() {
@@ -257,11 +262,11 @@ namespace Structure {
         hasNext: boolean;
         move(): Element.Location {
             this.current.element = this.elements[this.idx];
-            this.next();
+            this.advance();
             return this.current;
         }
 
-        private next() {
+        private advance() {
             if (this.idx < this.len - 1) {
                 this.idx++;
                 return;

+ 31 - 25
src/mol-model/structure/structure/unit.ts

@@ -5,13 +5,12 @@
  */
 
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
-import ElementGroup from './element/group'
 import { Model } from '../model'
-import { GridLookup3D } from 'mol-math/geometry'
-import { computeUnitBonds } from './element/properties/bonds/group-compute';
+import { GridLookup3D, Lookup3D } from 'mol-math/geometry'
 import CoarseGrained from '../model/properties/coarse-grained';
 import { SortedArray } from 'mol-data/int';
 import { idFactory } from 'mol-util/id-factory';
+import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds'
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -46,7 +45,9 @@ namespace Unit {
         readonly conformation: SymmetryOperator.ArrayMapping,
 
         getChild(elements: SortedArray): Unit,
-        applyOperator(id: number, operator: SymmetryOperator): Unit
+        applyOperator(id: number, operator: SymmetryOperator): Unit,
+
+        readonly lookup3d: Lookup3D
     }
 
     const unitIdFactory = idFactory();
@@ -72,6 +73,7 @@ namespace Unit {
         readonly chainIndex: ArrayLike<number>;
 
         getChild(elements: SortedArray): Unit {
+            if (elements.length === this.elements.length) return this;
             return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation);
         }
 
@@ -80,6 +82,21 @@ namespace Unit {
             return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomSiteConformation));
         }
 
+        private _lookup3d?: Lookup3D = void 0;
+        get lookup3d() {
+            if (this._lookup3d) return this._lookup3d;
+            const { x, y, z } = this.model.atomSiteConformation;
+            this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements });
+            return this._lookup3d;
+        }
+
+        private _bonds?: IntraUnitBonds = void 0;
+        get bonds() {
+            if (this._bonds) return this._bonds;
+            this._bonds = computeIntraUnitBonds(this);
+            return this._bonds;
+        }
+
         constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
             this.id = id;
             this.invariantId = invariantId;
@@ -108,6 +125,7 @@ namespace Unit {
         readonly sites: S;
 
         getChild(elements: SortedArray): Unit {
+            if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */;
             return createCoarse(this.id, this.invariantId, this.model, this.kind, this.sites, elements, this.conformation);
         }
 
@@ -116,6 +134,15 @@ namespace Unit {
             return createCoarse(id, this.invariantId, this.model, this.kind, this.sites, this.elements, SymmetryOperator.createMapping(op, this.sites));
         }
 
+        private _lookup3d?: Lookup3D = void 0;
+        get lookup3d() {
+            if (this._lookup3d) return this._lookup3d;
+            const { x, y, z } = this.sites;
+            // TODO: support sphere radius
+            this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements });
+            return this._lookup3d;
+        }
+
         constructor(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
             this.kind = kind;
             this.id = id;
@@ -133,27 +160,6 @@ namespace Unit {
 
     export interface Spheres extends CoarseBase<CoarseGrained.Spheres> { kind: Kind.Spheres }
     export interface Gaussians extends CoarseBase<CoarseGrained.Gaussians> { kind: Kind.Gaussians }
-
-    export function getLookup3d(unit: Unit, group: ElementGroup) {
-        if (group.__lookup3d__)  return group.__lookup3d__;
-        if (Unit.isAtomic(unit)) {
-            const { x, y, z } = unit.model.atomSiteConformation;
-            group.__lookup3d__ = GridLookup3D({ x, y, z, indices: group.elements });
-            return group.__lookup3d__;
-        }
-
-        throw 'not implemented';
-    }
-
-    export function getGroupBonds(unit: Unit, group: ElementGroup) {
-        if (group.__bonds__) return group.__bonds__;
-        if (Unit.isAtomic(unit)) {
-            group.__bonds__ = computeUnitBonds(unit, group);
-            return group.__bonds__;
-        }
-
-        throw 'not implemented';
-    }
 }
 
 export default Unit;

+ 8 - 0
src/mol-model/structure/structure/unit/bonds.ts

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export * from './bonds/intra-data'
+export * from './bonds/intra-compute'

+ 0 - 0
src/mol-model/structure/structure/unit/bonds/compute.ts


+ 0 - 0
src/mol-model/structure/structure/unit/bonds/data.ts


+ 13 - 14
src/mol-model/structure/structure/element/properties/bonds/group-compute.ts → src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -4,11 +4,10 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { BondType, ElementSymbol } from '../../../../model/types'
-import { GroupBonds } from './group-data'
-import { StructConn, ComponentBondInfo } from '../../../../model/formats/mmcif/bonds'
-import Unit from '../../../unit';
-import ElementGroup from '../../group';
+import { BondType, ElementSymbol } from '../../../model/types'
+import { IntraUnitBonds } from './intra-data'
+import { StructConn, ComponentBondInfo } from '../../../model/formats/mmcif/bonds'
+import Unit from '../../unit'
 
 export interface BondComputationParameters {
     maxHbondLength: number,
@@ -107,15 +106,15 @@ function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[],
     };
 }
 
-function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondComputationParameters): GroupBonds {
+function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds {
     const MAX_RADIUS = 3;
 
     const { x, y, z } = unit.model.atomSiteConformation;
-    const atomCount = ElementGroup.size(atoms);
-    const { residueIndex } = unit;
+    const atomCount = unit.elements.length;
+    const { elements: atoms, residueIndex } = unit;
     const { type_symbol, label_atom_id, label_alt_id } = unit.model.hierarchy.atoms;
     const { label_comp_id } = unit.model.hierarchy.residues;
-    const query3d = Unit.getLookup3d(unit, atoms);
+    const query3d = unit.lookup3d;
 
     const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.create(unit.model) : void 0
     const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(unit.model) : void 0
@@ -129,7 +128,7 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
     let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
 
     for (let _aI = 0; _aI < atomCount; _aI++) {
-        const aI = ElementGroup.getAt(atoms, _aI);
+        const aI =  atoms[_aI];
         const raI = residueIndex[aI];
 
         if (!params.forceCompute && raI !== lastResidue) {
@@ -155,7 +154,7 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
 
         for (let ni = 0; ni < count; ni++) {
             const _bI = indices[ni];
-            const bI = ElementGroup.getAt(atoms, _bI);
+            const bI = atoms[_bI];
             if (bI <= aI) continue;
 
             const altB = label_alt_id.value(bI);
@@ -243,11 +242,11 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
     };
 }
 
-function computeUnitBonds(unit: Unit.Atomic, atoms: ElementGroup, params?: Partial<BondComputationParameters>) {
-    return _computeBonds(unit, atoms, {
+function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<BondComputationParameters>) {
+    return _computeBonds(unit, {
         maxHbondLength: (params && params.maxHbondLength) || 1.15,
         forceCompute: !!(params && params.forceCompute),
     });
 }
 
-export { computeUnitBonds }
+export { computeIntraUnitBonds }

+ 5 - 5
src/mol-model/structure/structure/element/properties/bonds/group-data.ts → src/mol-model/structure/structure/unit/bonds/intra-data.ts

@@ -5,9 +5,9 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BondType } from '../../../../model/types'
+import { BondType } from '../../../model/types'
 
-interface GroupBonds {
+interface IntraUnitBonds {
     /**
      * Where bonds for atom A start and end.
      * Start offset at idx, end at idx + 1
@@ -21,8 +21,8 @@ interface GroupBonds {
     count: number
 }
 
-namespace GroupBonds {
-    export function createEmpty(): GroupBonds {
+namespace IntraUnitBonds {
+    export function createEmpty(): IntraUnitBonds {
         return { offset: [], neighbor: [], order: [], flags: [], count: 0 }
     }
     export function isCovalent(flags: number) {
@@ -46,4 +46,4 @@ namespace GroupBonds {
     }
 }
 
-export { GroupBonds }
+export { IntraUnitBonds }

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

@@ -1,6 +1,6 @@
 import * as B from 'benchmark'
 import { Tuple, Segmentation, OrderedSet as OrdSet } from 'mol-data/int'
-import { ElementSet } from 'mol-model/structure'
+//import { ElementSet } from 'mol-model/structure'
 
 // export namespace Iteration {
 //     const U = 1000, V = 2500;
@@ -198,39 +198,39 @@ export namespace Union {
     }
 }
 
-export namespace Build {
-    function createSorted() {
-        const b = ElementSet.LinearBuilder(ElementSet.Empty);
-        for (let i = 0; i < 10; i++) {
-            for (let j = 0; j < 1000; j++) {
-                b.add(i, j);
-            }
-        }
-        return b.getSet();
-    }
+// export namespace Build {
+//     function createSorted() {
+//         const b = ElementSet.LinearBuilder(ElementSet.Empty);
+//         for (let i = 0; i < 10; i++) {
+//             for (let j = 0; j < 1000; j++) {
+//                 b.add(i, j);
+//             }
+//         }
+//         return b.getSet();
+//     }
 
-    function createByUnit() {
-        const b = ElementSet.LinearBuilder(ElementSet.Empty);
-        for (let i = 0; i < 10; i++) {
-            b.beginUnit();
-            for (let j = 0; j < 1000; j++) {
-                b.addToUnit(j);
-            }
-            b.commitUnit(i);
-        }
-        return b.getSet();
-    }
+//     function createByUnit() {
+//         const b = ElementSet.LinearBuilder(ElementSet.Empty);
+//         for (let i = 0; i < 10; i++) {
+//             b.beginUnit();
+//             for (let j = 0; j < 1000; j++) {
+//                 b.addToUnit(j);
+//             }
+//             b.commitUnit(i);
+//         }
+//         return b.getSet();
+//     }
 
 
-    export function run() {
-        const suite = new B.Suite();
-        suite
-            .add('create sorted', () => createSorted())
-            .add('create by unit', () => createByUnit())
-            .on('cycle', (e: any) => console.log(String(e.target)))
-            .run();
-    }
-}
+//     export function run() {
+//         const suite = new B.Suite();
+//         suite
+//             .add('create sorted', () => createSorted())
+//             .add('create by unit', () => createByUnit())
+//             .on('cycle', (e: any) => console.log(String(e.target)))
+//             .run();
+//     }
+// }
 
 export namespace Tuples {
     function createData(n: number) {