diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts
index f3147c7086664c7900c78db8c89f1e87b15ff509..11a964b172a1a61070e24298af22d747150f9ff4 100644
--- a/src/mol-data/int/impl/sorted-array.ts
+++ b/src/mol-data/int/impl/sorted-array.ts
@@ -15,6 +15,12 @@ export const Empty: Nums = []
 export function ofSingleton(v: number) { return [v]; }
 export function ofSortedArray(xs: Nums) { return xs; }
 export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; }
+export function ofRange(min: number, max: number) {
+    if (max < min) return [];
+    const ret = new Int32Array(max - min + 1);
+    for (let i = min; i <= max; i++) ret[i - min] = i;
+    return ret;
+}
 export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
 
 export function start(xs: Nums) { return xs[0]; }
diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts
index 08235061224bfebfdf5f019281936e5c090bb65b..2b8391cb5cf929a7f45aa7e05da7b83f242431d1 100644
--- a/src/mol-data/int/sorted-array.ts
+++ b/src/mol-data/int/sorted-array.ts
@@ -12,6 +12,10 @@ namespace SortedArray {
     export const ofUnsortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofUnsortedArray as any;
     export const ofSingleton: (v: number) => SortedArray = Impl.ofSingleton as any;
     export const ofSortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofSortedArray as any;
+    // create sorted array [min, max] (it DOES contain the max value)
+    export const ofRange: (min: number, max: number) => SortedArray = Impl.ofRange as any;
+    // create sorted array [min, max) (it DOES not contain the max value)
+    export const ofBounds: (min: number, max: number) => SortedArray = (min, max) => Impl.ofRange(min, max - 1) as any;
     export const is: (v: any) => v is Interval = Impl.is as any;
 
     export const has: (array: SortedArray, x: number) => boolean = Impl.has as any;
diff --git a/src/mol-data/util/sort.ts b/src/mol-data/util/sort.ts
index e6eb6571ea82387a9e121e364ecb6528847d7da9..8068a449124aeb9d2b6dac403e0a4ba810fa8e2a 100644
--- a/src/mol-data/util/sort.ts
+++ b/src/mol-data/util/sort.ts
@@ -13,10 +13,10 @@ export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
     return arr[i] - arr[j];
 }
 
-export function arraySwap(arr: any[], i: number, j: number) {
+export function arraySwap(arr: ArrayLike<any>, i: number, j: number) {
     const temp = arr[i];
-    arr[i] = arr[j];
-    arr[j] = temp;
+    (arr as any[])[i] = arr[j];
+    (arr as any[])[j] = temp;
 }
 
 function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {
diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts
index 6b65fafd045d6bd31dcfbe1b2eb59308b44a37be..fe6cbc7aa04ca0deaa8869cb4afbe7998fc7669e 100644
--- a/src/mol-math/geometry/symmetry-operator.ts
+++ b/src/mol-math/geometry/symmetry-operator.ts
@@ -48,11 +48,11 @@ namespace SymmetryOperator {
 
     export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
 
-    export function createMapping(operator: SymmetryOperator, coords: Coordinates) {
+    export function createMapping(operator: SymmetryOperator, coords: Coordinates): ArrayMapping {
         const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
         const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
         const { x, y, z } = createProjections(operator, coords);
-        return { invariantPosition, position, x, y, z };
+        return { operator, invariantPosition, position, x, y, z };
     }
 
     export function createCoordinateMapper(t: SymmetryOperator, coords: Coordinates): CoordinateMapper {
diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index 3958a39faf09c85e7d07a2daca46530a396674df..65ded0cbbcb3c48f51824ca0649699f9c3f350e7 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -9,7 +9,7 @@ import { Column } from 'mol-data/db'
 import Iterator from 'mol-data/iterator'
 import * as Encoder from 'mol-io/writer/cif'
 // import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
-import { Structure, Element, ElementSet } from '../structure'
+import { Structure, Element } from '../structure'
 import { Model } from '../model'
 import P from '../query/properties'
 
@@ -113,8 +113,8 @@ function atomSiteProvider({ structure }: Context): Encoder.CategoryInstance {
     return {
         data: void 0,
         definition: atom_site,
-        keys: () => Structure.elementLocationsTransient(structure),
-        rowCount: ElementSet.elementCount(structure.elements)
+        keys: () => structure.elementLocations(),
+        rowCount: structure.elementCount
     }
 }
 
diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts
index 66c9f0415dd28f02c0f2ad74eac035ec09d305eb..c97533b54e0b03f1df9917eb0aaaf4ca0feda9d5 100644
--- a/src/mol-model/structure/query/generators.ts
+++ b/src/mol-model/structure/query/generators.ts
@@ -7,10 +7,10 @@
 import Query from './query'
 import Selection from './selection'
 import P from './properties'
-import { Structure, ElementSet, Element, Unit } from '../structure'
+import { Structure, Element, Unit } from '../structure'
 import { OrderedSet, Segmentation } from 'mol-data/int'
 
-export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s.elements);
+export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s);
 
 export interface AtomQueryParams {
     entityTest: Element.Predicate,
@@ -45,90 +45,86 @@ export function atoms(params?: Partial<AtomGroupsQueryParams>): Query.Provider {
 
 function atomGroupsLinear(atomTest: Element.Predicate): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
-        const builder = ElementSet.LinearBuilder(elements);
+        const builder = structure.subsetBuilder(true);
 
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            l.unit = units[unitId];
-            const set = ElementSet.groupAt(elements, i).elements;
+        for (const unit of units) {
+            l.unit = unit;
+            const elements = unit.elements;
 
-            builder.beginUnit();
-            for (let j = 0, _j = OrderedSet.size(set); j < _j; j++) {
-                l.element = OrderedSet.getAt(set, j);
-                if (atomTest(l)) builder.addToUnit(l.element);
+            builder.beginUnit(unit.id);
+            for (let j = 0, _j = elements.length; j < _j; j++) {
+                l.element = elements[j];
+                if (atomTest(l)) builder.addElement(l.element);
             }
-            builder.commitUnit(unitId);
+            builder.commitUnit();
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: units.length });
         }
 
-        return Selection.Singletons(structure, builder.getSet());
+        return Selection.Singletons(structure, builder.getStructure());
     };
 }
 
 function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
-        const builder = ElementSet.LinearBuilder(elements);
-
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            const unit = units[unitId];
+        const builder = structure.subsetBuilder(true);
 
+        for (const unit of units) {
             if (unit.kind !== Unit.Kind.Atomic) continue;
 
             l.unit = unit;
-            const set = ElementSet.groupAt(elements, i).elements;
+            const elements = unit.elements;
 
-            builder.beginUnit();
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
-            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);
+            builder.beginUnit(unit.id);
+            const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(set, chainSegment.start);
+                l.element = OrderedSet.getAt(elements, chainSegment.start);
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(set, residueSegment.start);
+                    l.element = OrderedSet.getAt(elements, residueSegment.start);
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(set, j);
-                        if (atomTest(l)) builder.addToUnit(l.element);
+                        l.element = OrderedSet.getAt(elements, j);
+                        if (atomTest(l)) {
+                            builder.addElement(l.element);
+                        }
                     }
                 }
             }
-            builder.commitUnit(unitId);
+            builder.commitUnit();
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: units.length });
         }
 
-        return Selection.Singletons(structure, builder.getSet());
+        return Selection.Singletons(structure, builder.getStructure());
     };
 }
 
 class LinearGroupingBuilder {
-    private builders: ElementSet.Builder[] = [];
-    private builderMap = new Map<string, ElementSet.Builder>();
+    private builders: Structure.SubsetBuilder[] = [];
+    private builderMap = new Map<string, Structure.SubsetBuilder>();
 
-    add(key: any, unit: number, atom: number) {
+    add(key: any, unit: number, element: number) {
         let b = this.builderMap.get(key);
         if (!b) {
-            b = ElementSet.LinearBuilder(this.structure.elements);
+            b = this.source.subsetBuilder(true);
             this.builders[this.builders.length] = b;
             this.builderMap.set(key, b);
         }
-        b.add(unit, atom);
+        b.addToUnit(unit, element);
     }
 
     private allSingletons() {
@@ -139,71 +135,68 @@ class LinearGroupingBuilder {
     }
 
     private singletonSelection(): Selection {
-        const atoms: Element[] = Element.createEmptyArray(this.builders.length);
+        const builder = this.source.subsetBuilder(true);
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            atoms[i] = this.builders[i].singleton();
+            const e = this.builders[i].singleton();
+            builder.addToUnit(Element.unit(e), Element.index(e));
         }
-        return Selection.Singletons(this.structure, ElementSet.ofAtoms(atoms, this.structure.elements));
+        return Selection.Singletons(this.source, builder.getStructure());
     }
 
     private fullSelection() {
-        const sets: ElementSet[] = new Array(this.builders.length);
+        const structures: Structure[] = new Array(this.builders.length);
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            sets[i] = this.builders[i].getSet();
+            structures[i] = this.builders[i].getStructure();
         }
-        return Selection.Sequence(this.structure, sets);
+        return Selection.Sequence(this.source, structures);
     }
 
     getSelection(): Selection {
         const len = this.builders.length;
-        if (len === 0) return Selection.Empty(this.structure);
+        if (len === 0) return Selection.Empty(this.source);
         if (this.allSingletons()) return this.singletonSelection();
         return this.fullSelection();
     }
 
-    constructor(private structure: Structure) { }
+    constructor(private source: Structure) { }
 }
 
 function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
         const builder = new LinearGroupingBuilder(structure);
 
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            const unit = units[unitId];
-
+        for (const unit of units) {
             if (unit.kind !== Unit.Kind.Atomic) continue;
 
             l.unit = unit;
-            const set = ElementSet.groupAt(elements, i).elements;
+            const elements = unit.elements;
 
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
-            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);
+            const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(set, chainSegment.start);
+                l.element = OrderedSet.getAt(elements, chainSegment.start);
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(set, residueSegment.start);
+                    l.element = OrderedSet.getAt(elements, residueSegment.start);
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(set, j);
-                        if (atomTest(l)) builder.add(groupBy(l), unitId, l.element);
+                        l.element = OrderedSet.getAt(elements, j);
+                        if (atomTest(l)) builder.add(groupBy(l), unit.id, l.element);
                     }
                 }
             }
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: units.length });
         }
 
         return builder.getSelection();
diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts
index 1381966083177d1216ba3603561979fbb20d1453..fc2a066ad957730fe49ec9a884ffb47a822156e6 100644
--- a/src/mol-model/structure/query/properties.ts
+++ b/src/mol-model/structure/query/properties.ts
@@ -26,41 +26,41 @@ const atom = {
     key: Element.property(l => l.element),
 
     // Conformation
-    x: Element.property(l => l.unit.x(l.element)),
-    y: Element.property(l => l.unit.y(l.element)),
-    z: Element.property(l => l.unit.z(l.element)),
-    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.conformation.atomId.value(l.element)),
-    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.conformation.occupancy.value(l.element)),
-    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.conformation.B_iso_or_equiv.value(l.element)),
+    x: Element.property(l => l.unit.conformation.x(l.element)),
+    y: Element.property(l => l.unit.conformation.y(l.element)),
+    z: Element.property(l => l.unit.conformation.z(l.element)),
+    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomSiteConformation.atomId.value(l.element)),
+    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomSiteConformation.occupancy.value(l.element)),
+    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomSiteConformation.B_iso_or_equiv.value(l.element)),
 
     // Hierarchy
-    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
+    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.type_symbol.value(l.element)),
+    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_atom_id.value(l.element)),
+    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.auth_atom_id.value(l.element)),
+    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_alt_id.value(l.element)),
+    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
 
     // Derived
-    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
+    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.hierarchy.atoms.type_symbol.value(l.element))),
 }
 
 const residue = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residueKey.value(l.unit.residueIndex[l.element])),
-
-    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
-    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
-    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
-    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
-    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element]))
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residueKey.value(l.unit.residueIndex[l.element])),
+
+    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
+    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
+    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
+    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element]))
 }
 
 const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chainKey.value(l.unit.chainIndex[l.element])),
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chainKey.value(l.unit.chainIndex[l.element])),
 
-    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
-    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
-    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
+    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
+    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
+    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
 }
 
 const coarse_grained = {
@@ -83,7 +83,7 @@ const coarse_grained = {
     gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.sites.covariance_matrix.value(l.element))
 }
 
-function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.entityKey.value(l.unit.chainIndex[l.element]); }
+function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.entityKey.value(l.unit.chainIndex[l.element]); }
 
 const entity = {
     key: eK,
@@ -101,7 +101,7 @@ const entity = {
 }
 
 const unit = {
-    operator_name: Element.property(l => l.unit.operator.name),
+    operator_name: Element.property(l => l.unit.conformation.operator.name),
     model_num: Element.property(l => l.unit.model.modelNum)
 }
 
diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts
index f9f516710817ea7ae9d8c042457a7f81e2f53a2d..02235ee8eeb147c52b50f7ed385eb60504f67e37 100644
--- a/src/mol-model/structure/query/selection.ts
+++ b/src/mol-model/structure/query/selection.ts
@@ -5,102 +5,76 @@
  */
 
 import { HashSet } from 'mol-data/generic'
-import { Structure, ElementSet } from '../structure'
+import { Structure } from '../structure'
+import { SortedArray } from 'mol-data/int';
 
 // A selection is a pair of a Structure and a sequence of unique AtomSets
 type Selection = Selection.Singletons | Selection.Sequence
 
 namespace Selection {
     // If each element of the selection is a singleton, we can use a more efficient representation.
-    export interface Singletons { readonly kind: 'singletons', readonly structure: Structure, readonly set: ElementSet }
-    export interface Sequence { readonly kind: 'sequence', readonly structure: Structure, readonly sets: ReadonlyArray<ElementSet> }
+    export interface Singletons { readonly kind: 'singletons', readonly source: Structure, readonly structure: Structure }
+    export interface Sequence { readonly kind: 'sequence', readonly source: Structure, readonly structures: Structure[] }
 
-    export function Singletons(structure: Structure, set: ElementSet): Singletons { return { kind: 'singletons', structure, set } }
-    export function Sequence(structure: Structure, sets: ElementSet[]): Sequence { return { kind: 'sequence', structure, sets } }
-    export function Empty(structure: Structure): Selection { return Sequence(structure, []); };
+    export function Singletons(source: Structure, structure: Structure): Singletons { return { kind: 'singletons', source, structure } }
+    export function Sequence(source: Structure, structures: Structure[]): Sequence { return { kind: 'sequence', source, structures } }
+    export function Empty(source: Structure): Selection { return Singletons(source, Structure.Empty); };
 
     export function isSingleton(s: Selection): s is Singletons { return s.kind === 'singletons'; }
-    export function isEmpty(s: Selection) { return isSingleton(s) ? ElementSet.elementCount(s.set) === 0 : s.sets.length === 0; }
+    export function isEmpty(s: Selection) { return isSingleton(s) ? s.structure.units.length === 0 : s.structures.length === 0; }
 
     export function structureCount(sel: Selection) {
-        if (isSingleton(sel)) return ElementSet.elementCount(sel.set);
-        return sel.sets.length;
+        if (isSingleton(sel)) return sel.structure.elementCount;
+        return sel.structures.length;
     }
 
     export function unionStructure(sel: Selection): Structure {
-        if (isEmpty(sel)) return Structure.Empty(sel.structure.units);
-        if (isSingleton(sel)) return Structure.create(sel.structure.units, sel.set);
-        return Structure.create(sel.structure.units, ElementSet.union(sel.sets, sel.structure.elements));
-    }
-
-    export function getAt(sel: Selection, i: number): Structure {
-        if (isSingleton(sel)) {
-            const atom = ElementSet.elementAt(sel.set, i);
-            return Structure.create(sel.structure.units, ElementSet.singleton(atom, sel.structure.elements));
-        }
-        return Structure.create(sel.structure.units, sel.sets[i]);
-    }
-
-    export function toStructures(sel: Selection): Structure[] {
-        const { units } = sel.structure;
-        if (isSingleton(sel)) {
-            const ret: Structure[] = new Array(ElementSet.elementCount(sel.set));
-            const atoms = ElementSet.elements(sel.set);
-            let offset = 0;
-            while (atoms.hasNext) {
-                const atom = atoms.move();
-                ret[offset++] = Structure.create(units, ElementSet.singleton(atom, sel.structure.elements))
-            }
-            return ret;
-        } else {
-            const { sets } = sel;
-            const ret: Structure[] = new Array(sets.length);
-            for (let i = 0, _i = sets.length; i < _i; i++) ret[i] = Structure.create(units, sets[i]);
-            return ret;
-        }
+        if (isEmpty(sel)) return Structure.Empty;
+        if (isSingleton(sel)) return sel.structure;
+        return union(sel.source, sel.structures);
     }
 
     export interface Builder {
-        add(set: ElementSet): void,
+        add(structure: Structure): void,
         getSelection(): Selection
     }
 
-    function getSelection(structure: Structure, sets: ElementSet[], allSingletons: boolean) {
-        const len = sets.length;
-        if (len === 0) return Empty(structure);
-        if (allSingletons) return Singletons(structure, ElementSet.union(sets, structure.elements));
-        return Sequence(structure, sets);
+    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));
+        return Sequence(source, structures);
     }
 
     class LinearBuilderImpl implements Builder {
-        private sets: ElementSet[] = [];
+        private structures: Structure[] = [];
         private allSingletons = true;
 
-        add(atoms: ElementSet) {
-            const atomCount = ElementSet.elementCount(atoms);
-            if (atomCount === 0) return;
-            this.sets[this.sets.length] = atoms;
-            if (atomCount !== 1) this.allSingletons = false;
+        add(structure: Structure) {
+            const elementCount = structure.elementCount;
+            if (elementCount === 0) return;
+            this.structures[this.structures.length] = structure;
+            if (elementCount !== 1) this.allSingletons = false;
         }
 
-        getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); }
+        getSelection() { return getSelection(this.source, this.structures, this.allSingletons); }
 
-        constructor(private structure: Structure) { }
+        constructor(private source: Structure) { }
     }
 
     class HashBuilderImpl implements Builder {
-        private sets: ElementSet[] = [];
+        private structures: Structure[] = [];
         private allSingletons = true;
-        private uniqueSets = HashSet(ElementSet.hashCode, ElementSet.areEqual);
+        private uniqueSets = HashSet(Structure.hashCode, Structure.areEqual);
 
-        add(atoms: ElementSet) {
-            const atomCount = ElementSet.elementCount(atoms);
-            if (atomCount === 0 || !this.uniqueSets.add(atoms)) return;
-            this.sets[this.sets.length] = atoms;
+        add(structure: Structure) {
+            const atomCount = structure.elementCount;
+            if (atomCount === 0 || !this.uniqueSets.add(structure)) return;
+            this.structures[this.structures.length] = structure;
             if (atomCount !== 1) this.allSingletons = false;
         }
 
-        getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); }
+        getSelection() { return getSelection(this.structure, this.structures, this.allSingletons); }
 
         constructor(private structure: Structure) { }
     }
@@ -109,6 +83,38 @@ 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
\ No newline at end of file
diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts
index 59f200b700ccd406f08a897a847e84b60acc7bee..0a3826b643d288bcd12f81ef695a288a68b07f5a 100644
--- a/src/mol-model/structure/structure.ts
+++ b/src/mol-model/structure/structure.ts
@@ -5,10 +5,8 @@
  */
 
 import Element from './structure/element'
-import ElementSet from './structure/element/set'
-import ElementGroup from './structure/element/group'
 import Structure from './structure/structure'
 import Unit from './structure/unit'
 import Symmetry from './structure/symmetry'
 
-export { Element, ElementSet, ElementGroup, Structure, Unit, Symmetry }
\ No newline at end of file
+export { Element, Structure, Unit, Symmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/group.ts b/src/mol-model/structure/structure/element/group.ts
index e89b3d37e87b6611a0630fe4978fe1218ceaa76b..04c4e12ac140962f559d46478e8313e287b102c8 100644
--- a/src/mol-model/structure/structure/element/group.ts
+++ b/src/mol-model/structure/structure/element/group.ts
@@ -30,7 +30,7 @@ namespace ElementGroup {
     }
 
     export function create(unit: Unit, elements: OrderedSet): ElementGroup {
-        if (OrderedSet.areEqual(elements, unit.fullGroup.elements)) return unit.fullGroup;
+        //if (OrderedSet.areEqual(elements, unit.fullGroup.elements)) return unit.fullGroup;
         return createNew(elements);
     }
 
diff --git a/src/mol-model/structure/structure/element/impl/set.ts b/src/mol-model/structure/structure/element/impl/set.ts
index 2aaf3182e5f1ea62f0c313357d3b48d6b8a5dd4c..17ada0b577a782c9699697a31b01764f10396c14 100644
--- a/src/mol-model/structure/structure/element/impl/set.ts
+++ b/src/mol-model/structure/structure/element/impl/set.ts
@@ -9,18 +9,13 @@ import { sortArray } from 'mol-data/util/sort'
 import { hash1 } from 'mol-data/util/hash-functions'
 import Element from '../../element'
 import ElementGroup from '../group'
-import { ElementSetLookup3D } from '../../util/lookup3d'
-import Structure from '../../structure';
-
 /** Long and painful implementation starts here */
 
 export type ElementSetImpl = {
     groups: IntMap<ElementGroup>,
     offsets: Int32Array,
     hashCode: number,
-    keys: SortedArray,
-
-    __lookup3d__?: ElementSetLookup3D
+    keys: SortedArray
 }
 
 export const Empty: ElementSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
@@ -279,13 +274,6 @@ export function Generator() {
     return new AtomSetGenerator();
 }
 
-export function getLookup3d(s: Structure) {
-    const set = s.elements as any as ElementSetImpl;
-    if (set.__lookup3d__) return set.__lookup3d__;
-    set.__lookup3d__ = ElementSetLookup3D.create(s);
-    return set.__lookup3d__;
-}
-
 /** When adding groups, compare them to existing ones. If they all match, return the whole original set. */
 class ChildGenerator {
     private keys: number[] = [];
diff --git a/src/mol-model/structure/structure/element/set.ts b/src/mol-model/structure/structure/element/set.ts
index dddebe8bfd28b73505e0c93d18840a68087d83d4..0482f2d9edd3a38964eca9092a48131c2ec8daeb 100644
--- a/src/mol-model/structure/structure/element/set.ts
+++ b/src/mol-model/structure/structure/element/set.ts
@@ -9,7 +9,7 @@ import Element from '../element'
 import ElementGroup from './group'
 import * as Impl from './impl/set'
 import * as Builders from './impl/set-builder'
-import { ElementSetLookup3D } from '../util/lookup3d';
+import { StructureLookup3D } from '../util/lookup3d';
 import Structure from '../structure';
 
 /**
@@ -55,8 +55,6 @@ namespace ElementSet {
     export interface TemplateGenerator { add(unit: number, set: OrderedSet): void, getSet(): ElementSet }
     export const TemplateGenerator: (template: ElementSet) => TemplateGenerator = Impl.TemplateGenerator as any
 
-    export const getLookup3d: (s: Structure) => ElementSetLookup3D = Impl.getLookup3d;
-
     // TODO: distance, areWithIn?
     // TODO: check connected
     // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 4447c1936c1b78c89043b000403207434cde3a3c..c3df4747734b34827515145a7ec48d6c8566702d 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -4,25 +4,75 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { OrderedSet, Iterator } from 'mol-data/int'
+import { IntMap, SortedArray, Iterator } from 'mol-data/int'
 import { UniqueArray } from 'mol-data/generic'
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { Model, Format } from '../model'
-import Unit from './unit'
-import ElementSet from './element/set'
-import ElementGroup from './element/group'
+import { sortArray, sort, arraySwap, hash1 } from 'mol-data/util';
 import Element from './element'
+import Unit from './unit'
+import { StructureLookup3D } from './util/lookup3d';
 
 // A structure is a pair of "units" and an element set.
 // Each unit contains the data and transformation of its corresponding elements.
-interface Structure {
-    readonly units: ReadonlyArray<Unit>,
-    readonly elements: ElementSet
+class Structure {
+    readonly unitMap: IntMap<Unit>;
+    readonly units: ReadonlyArray<Unit>;
+    readonly elementCount: number;
+
+    private _hashCode = 0;
+
+    subsetBuilder(isSorted: boolean) {
+        return new Structure.SubsetBuilder(this, isSorted);
+    }
+
+    get hashCode() {
+        if (this._hashCode !== 0) return this._hashCode;
+        return this.computeHash();
+    }
+
+    private computeHash() {
+        let hash = 23;
+        for (let i = 0, _i = this.units.length; i < _i; i++) {
+            const u = this.units[i];
+            hash = (31 * hash + u.id) | 0;
+            hash = (31 * hash + SortedArray.hashCode(u.elements)) | 0;
+        }
+        hash = (31 * hash + this.elementCount) | 0;
+        hash = hash1(hash);
+        this._hashCode = hash;
+        return hash;
+    }
+
+    elementLocations(): Iterator<Element.Location> {
+        return new Structure.ElementLocationIterator(this);
+    }
+
+    constructor(units: ArrayLike<Unit>) {
+        const map = IntMap.Mutable<Unit>();
+        let elementCount = 0;
+        let isSorted = true;
+        let lastId = units.length > 0 ? units[0].id : 0;
+        for (let i = 0, _i = units.length; i < _i; i++) {
+            const u = units[i];
+            map.set(u.id, u);
+            elementCount += u.elements.length;
+            if (u.id < lastId) isSorted = false;
+            lastId = u.id;
+        }
+        if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap)
+        this.unitMap = map;
+        this.units = units as ReadonlyArray<Unit>;
+        this.elementCount = elementCount;
+    }
 }
 
+function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
+
 namespace Structure {
-    export function create(units: ReadonlyArray<Unit>, elements: ElementSet): Structure { return { units, elements }; }
-    export function Empty(units: ReadonlyArray<Unit>): Structure { return create(units, ElementSet.Empty); };
+    export const Empty = new Structure([]);
+
+    export function create(units: ReadonlyArray<Unit>): Structure { return new Structure(units); }
 
     export function ofData(format: Format) {
         const models = Model.create(format);
@@ -31,81 +81,213 @@ namespace Structure {
 
     export function ofModel(model: Model): Structure {
         const chains = model.hierarchy.chainSegments;
-        const builder = Builder();
+        const builder = new StructureBuilder();
 
         for (let c = 0; c < chains.count; c++) {
-            const group = ElementGroup.createNew(OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
-            const unit = Unit.create(Unit.Kind.Atomic, model, SymmetryOperator.Default, group);
-            builder.add(unit, unit.fullGroup);
+            const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]);
+            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
         }
 
         const cs = model.coarseGrained;
         if (cs.isDefined) {
             if (cs.spheres.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.spheres.count));
-                const unit = Unit.create(Unit.Kind.Spheres, model, SymmetryOperator.Default, group);
-                builder.add(unit, unit.fullGroup);
+                const elements = SortedArray.ofBounds(0, cs.spheres.count);
+                builder.addUnit(Unit.Kind.Spheres, model, SymmetryOperator.Default, elements);
             }
             if (cs.gaussians.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.gaussians.count));
-                const unit = Unit.create(Unit.Kind.Gaussians, model, SymmetryOperator.Default, group);
-                builder.add(unit, unit.fullGroup);
+                const elements = SortedArray.ofBounds(0, cs.spheres.count);
+                builder.addUnit(Unit.Kind.Gaussians, model, SymmetryOperator.Default, elements);
             }
         }
 
         return builder.getStructure();
     }
 
-    export interface Builder {
-        add(unit: Unit, elements: ElementGroup): void,
-        addUnit(unit: Unit): void,
-        setElements(unitId: number, elements: ElementGroup): void,
-        getStructure(): Structure,
-        readonly elementCount: number
+    export class StructureBuilder {
+        private units: Unit[] = [];
+
+        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+            const unit = Unit.create(this.units.length, kind, model, operator, elements);
+            this.units.push(unit);
+            return unit;
+        }
+
+        addWithOperator(unit: Unit, operator: SymmetryOperator): Unit {
+            const newUnit = unit.applyOperator(this.units.length, operator);
+            this.units.push(newUnit);
+            return newUnit;
+        }
+
+        getStructure(): Structure {
+            return create(this.units);
+        }
+
+        get isEmpty() {
+            return this.units.length === 0;
+        }
     }
 
-    class BuilderImpl implements Builder {
-        private _unitId = 0;
-        private units: Unit[] = [];
-        private elements = ElementSet.Generator();
+    export function Builder() { return new StructureBuilder(); }
+
+    export class SubsetBuilder {
+        private ids: number[] = [];
+        private unitMap = IntMap.Mutable<number[]>();
+        private parentId = -1;
+        private currentUnit: number[] = [];
         elementCount = 0;
 
-        add(unit: Unit, elements: ElementGroup) { const id = this.addUnit(unit); this.setElements(id, elements); }
-        addUnit(unit: Unit) { const id = this._unitId++; this.units[id] = unit; return id; }
-        setElements(unitId: number, elements: ElementGroup) { this.elements.add(unitId, elements); this.elementCount += ElementGroup.size(elements); }
-        getStructure(): Structure { return this.elementCount > 0 ? Structure.create(this.units, this.elements.getSet()) : Empty(this.units); }
-    }
+        addToUnit(parentId: number, e: number) {
+            const unit = this.unitMap.get(parentId);
+            if (!!unit) { unit[unit.length] = e; }
+            else {
+                this.unitMap.set(parentId, [e]);
+                this.ids[this.ids.length] = parentId;
+            }
+            this.elementCount++;
+        }
 
-    export function Builder(): Builder { return new BuilderImpl(); }
+        beginUnit(parentId: number) {
+            this.parentId = parentId;
+            this.currentUnit = this.currentUnit.length > 0 ? [] : this.currentUnit;
+        }
+
+        addElement(e: number) {
+            this.currentUnit[this.currentUnit.length] = e;
+            this.elementCount++;
+        }
+
+        commitUnit() {
+            if (this.currentUnit.length === 0) return;
+            this.ids[this.ids.length] = this.parentId;
+            this.unitMap.set(this.parentId, this.currentUnit);
+            this.parentId = -1;
+        }
+
+        setUnit(parentId: number, elements: ArrayLike<number>) {
+            this.ids[this.ids.length] = parentId;
+            this.unitMap.set(parentId, elements as number[]);
+            this.elementCount += elements.length;
+        }
 
-    /** Transient = location gets overwritten when move() is called. */
-    export function elementLocationsTransient(s: Structure): Iterator<Element.Location> {
-        const l = Element.Location();
-        const update = Element.updateLocation;
-        return Iterator.map(ElementSet.elements(s.elements), a => update(s, l, a));
+        getStructure(): Structure {
+            if (this.isEmpty) return Structure.Empty;
+
+            const newUnits: Unit[] = [];
+            sortArray(this.ids);
+
+            for (let i = 0, _i = this.ids.length; i < _i; i++) {
+                const id = this.ids[i];
+                const parent = this.parent.unitMap.get(id);
+
+                const unit = this.unitMap.get(id);
+                const l = unit.length;
+
+                // if the length is the same, just copy the old unit.
+                if (unit.length === parent.elements.length) {
+                    newUnits[newUnits.length] = parent;
+                    continue;
+                }
+
+                if (!this.isSorted && l > 1) sortArray(unit);
+
+                // TODO: checking if two units are equal in the same symmetry group to reuse computed properties.
+                newUnits[newUnits.length] = parent.getChild(SortedArray.ofSortedArray(unit));
+            }
+
+            return create(newUnits);
+        }
+
+        singleton(): Element {
+            return Element.create(this.ids[0], this.unitMap.get(this.ids[0])[0]);
+        }
+
+        get isEmpty() {
+            return this.elementCount === 0;
+        }
+
+        constructor(private parent: Structure, private isSorted: boolean) {
+
+        }
     }
 
     export function getModels(s: Structure) {
-        const { units, elements } = s;
+        const { units } = s;
         const arr = UniqueArray.create<Model['id'], Model>();
-        const ids = ElementSet.unitIndices(elements);
-        for (let i = 0; i < ids.length; i++) {
-            const u = units[ids[i]];
+        for (const u of units) {
             UniqueArray.add(arr, u.model.id, u.model);
         }
         return arr.array;
     }
 
-    export function getLookup3d(s: Structure) {
-        return ElementSet.getLookup3d(s);
+    export function getLookup3d(s: Structure): StructureLookup3D {
+        return 0 as any;
     }
 
     export function getBoundary(s: Structure) {
         return getLookup3d(s).boundary;
     }
 
-    // TODO: "lift" atom set operators?
-    // TODO: "diff"
+    export function hashCode(s: Structure) {
+        return s.hashCode;
+    }
+
+    export function areEqual(a: Structure, b: Structure) {
+        if (a.elementCount !== b.elementCount) return false;
+        const len = a.units.length;
+        if (len !== b.units.length) return false;
+
+        for (let i = 0; i < len; i++) {
+            if (a.units[i].id !== b.units[i].id) return false;
+        }
+
+        for (let i = 0; i < len; i++) {
+            if (!SortedArray.areEqual(a.units[i].elements, b.units[i].elements)) return false;
+        }
+
+        return true;
+    }
+
+    export class ElementLocationIterator implements Iterator<Element.Location> {
+        private current = Element.Location();
+        private unitIndex = 0;
+        private elements: SortedArray;
+        private len = 0;
+        private idx = 0;
+
+        hasNext: boolean;
+        move(): Element.Location {
+            this.current.element = this.elements[this.idx];
+            this.next();
+            return this.current;
+        }
+
+        private next() {
+            if (this.idx < this.len - 1) {
+                this.idx++;
+                return;
+            }
+
+            this.idx = 0;
+            this.unitIndex++;
+            if (this.unitIndex >= this.structure.units.length) {
+                this.hasNext = false;
+                return;
+            }
+
+            this.current.unit = this.structure.units[this.unitIndex];
+            this.elements = this.current.unit.elements;
+            this.len = this.elements.length;
+        }
+
+        constructor(private structure: Structure) {
+            this.hasNext = structure.elementCount > 0;
+            if (this.hasNext) {
+                this.elements = structure.units[0].elements;
+                this.len = this.elements.length;
+                this.current.unit = structure.units[0];
+            }
+        }
+    }
 }
 
 export default Structure
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts
index 7fe2fb2bb874e1402537df5ec312c3df5677e5ed..35f92e9d687e733aa2750e2de9e0e93d1cd79fd9 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -5,8 +5,6 @@
  */
 
 import Structure from './structure'
-import ElementSet from './element/set'
-import Unit from './unit'
 import { Selection } from '../query'
 import { ModelSymmetry } from '../model'
 import { Task } from 'mol-task';
@@ -29,15 +27,14 @@ function buildAssemblyImpl(structure: Structure, name: string) {
 
         for (const g of assembly.operatorGroups) {
             const selection = await ctx.runChild(g.selector(structure));
-            if (Selection.structureCount(selection) === 0) continue;
-            const { units, elements } = Selection.unionStructure(selection);
-
-            const unitIds = ElementSet.unitIndices(elements);
+            if (Selection.structureCount(selection) === 0) {
+                continue;
+            }
+            const { units } = Selection.unionStructure(selection);
 
             for (const oper of g.operators) {
-                for (let uI = 0, _uI = unitIds.length; uI < _uI; uI++) {
-                    const unit = units[unitIds[uI]];
-                    assembler.add(Unit.withOperator(unit, oper), ElementSet.groupAt(elements, uI));
+                for (const unit of units) {
+                    assembler.addWithOperator(unit, oper);
                 }
             }
         }
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index 3c82688a421d1aa5570778b92d753ace961d3b8c..3783235b27db700584f03ca632b698b2f528514c 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -10,6 +10,8 @@ import { Model } from '../model'
 import { GridLookup3D } from 'mol-math/geometry'
 import { computeUnitBonds } from './element/properties/bonds/group-compute';
 import CoarseGrained from '../model/properties/coarse-grained';
+import { SortedArray } from 'mol-data/int';
+import { idFactory } from 'mol-util/id-factory';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -23,18 +25,32 @@ namespace Unit {
     export function isSpheres(u: Unit): u is Spheres { return u.kind === Kind.Spheres; }
     export function isGaussians(u: Unit): u is Gaussians { return u.kind === Kind.Gaussians; }
 
-    export interface Base extends SymmetryOperator.ArrayMapping {
-        // Provides access to the underlying data.
+    export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+        switch (kind) {
+            case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
+            case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, model.coarseGrained.spheres, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
+            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, model.coarseGrained.gaussians, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
+        }
+    }
+
+    export function applyOperator(id: number, unit: Unit, operator: SymmetryOperator): Unit {
+        return create(id, unit.kind, unit.model, SymmetryOperator.compose(unit.conformation.operator, operator), unit.elements);
+    }
+
+    export interface Base {
+        readonly id: number,
+        // invariant ID stays the same even if the Operator/conformation changes.
+        readonly invariantId: number,
+        readonly elements: SortedArray,
         readonly model: Model,
+        readonly conformation: SymmetryOperator.ArrayMapping,
 
-        // The "full" atom group corresponding to this unit.
-        // Every selection is a subset of this atoms group.
-        // Things like inter-unit bonds or spatial lookups
-        // can be be implemented efficiently as "views" of the
-        // full group.
-        readonly fullGroup: ElementGroup
+        getChild(elements: SortedArray): Unit,
+        applyOperator(id: number, operator: SymmetryOperator): Unit
     }
 
+    const unitIdFactory = idFactory();
+
     // A bulding block of a structure that corresponds
     // to a "natural group of atoms" (most often a "chain")
     // together with a tranformation (rotation and translation)
@@ -42,85 +58,81 @@ namespace Unit {
     //
     // An atom set can be referenced by multiple diffrent units which
     // makes construction of assemblies and spacegroups very efficient.
-    export interface Atomic extends Base {
-        readonly kind: Kind.Atomic,
+    export class Atomic implements Base {
+        readonly kind = Kind.Atomic;
+
+        readonly id: number;
+        readonly invariantId: number;
+        readonly elements: SortedArray;
+        readonly model: Model;
+        readonly conformation: SymmetryOperator.ArrayMapping;
 
         // Reference some commonly accessed things for faster access.
-        readonly residueIndex: ArrayLike<number>,
-        readonly chainIndex: ArrayLike<number>,
-        readonly conformation: Model['atomSiteConformation'],
-        readonly hierarchy: Model['hierarchy']
+        readonly residueIndex: ArrayLike<number>;
+        readonly chainIndex: ArrayLike<number>;
+
+        getChild(elements: SortedArray): Unit {
+            return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation);
+        }
+
+        applyOperator(id: number, operator: SymmetryOperator): Unit {
+            const op = SymmetryOperator.compose(this.conformation.operator, operator);
+            return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomSiteConformation));
+        }
+
+        constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+
+            this.residueIndex = model.hierarchy.residueSegments.segmentMap;
+            this.chainIndex = model.hierarchy.chainSegments.segmentMap;
+        }
     }
 
     // Coarse grained representations.
-    export interface CoarseBase<K extends Kind, S extends CoarseGrained.SitesBase> extends Base  {
-        readonly kind: K,
+    export interface CoarseBase<S extends CoarseGrained.SitesBase> extends Base  {
         readonly sites: S
     }
 
-    export interface Spheres extends CoarseBase<Kind.Spheres, CoarseGrained.Spheres> { }
-    export interface Gaussians extends CoarseBase<Kind.Gaussians, CoarseGrained.Gaussians> { }
+    class Coarse<S extends CoarseGrained.SitesBase> implements CoarseBase<S> {
+        readonly kind: Kind;
 
-    export function create(kind: Kind, model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit {
-        switch (kind) {
-            case Kind.Atomic: return createAtomic(model, operator, fullGroup);
-            case Kind.Spheres: return createSpheres(model, operator, fullGroup);
-            case Kind.Gaussians: return createGaussians(model, operator, fullGroup);
+        readonly id: number;
+        readonly invariantId: number;
+        readonly elements: SortedArray;
+        readonly model: Model;
+        readonly conformation: SymmetryOperator.ArrayMapping;
+        readonly sites: S;
+
+        getChild(elements: SortedArray): Unit {
+            return createCoarse(this.id, this.invariantId, this.model, this.kind, this.sites, elements, this.conformation);
         }
-    }
 
-    function createAtomic(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit.Atomic {
-        const h = model.hierarchy;
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.atomSiteConformation);
-
-        return {
-            model,
-            kind: Kind.Atomic,
-            operator,
-            fullGroup,
-            residueIndex: h.residueSegments.segmentMap,
-            chainIndex: h.chainSegments.segmentMap,
-            hierarchy: model.hierarchy,
-            conformation: model.atomSiteConformation,
-            invariantPosition,
-            position,
-            x, y, z
-        };
-    }
+        applyOperator(id: number, operator: SymmetryOperator): Unit {
+            const op = SymmetryOperator.compose(this.conformation.operator, operator);
+            return createCoarse(id, this.invariantId, this.model, this.kind, this.sites, this.elements, SymmetryOperator.createMapping(op, this.sites));
+        }
 
-    function createSpheres(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit.Spheres {
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.coarseGrained.spheres);
-
-        return {
-            model,
-            kind: Kind.Spheres,
-            sites: model.coarseGrained.spheres,
-            operator,
-            fullGroup,
-            invariantPosition,
-            position,
-            x, y, z
-        };
+        constructor(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+            this.kind = kind;
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+            this.sites = sites;
+        }
     }
 
-    function createGaussians(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit.Gaussians {
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.coarseGrained.gaussians);
-
-        return {
-            model,
-            kind: Kind.Gaussians,
-            sites: model.coarseGrained.gaussians,
-            operator,
-            fullGroup,
-            invariantPosition,
-            position,
-            x, y, z
-        };
+    function createCoarse<S extends CoarseGrained.SitesBase>(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
+        return new Coarse(id, invariantId, model, kind, sites, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
     }
 
-    export function withOperator(unit: Unit, operator: SymmetryOperator): Unit {
-        return create(unit.kind, unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup);
-    }
+    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__;
diff --git a/src/mol-model/structure/structure/unit/bonds/compute.ts b/src/mol-model/structure/structure/unit/bonds/compute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/mol-model/structure/structure/unit/bonds/data.ts b/src/mol-model/structure/structure/unit/bonds/data.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/mol-model/structure/structure/util/boundary.ts b/src/mol-model/structure/structure/util/boundary.ts
index 547d3828eb2f892bcdb9b8b620cebe36fc0a0610..2a595f8cd7551a060e19d12388e7ad222f55dfd7 100644
--- a/src/mol-model/structure/structure/util/boundary.ts
+++ b/src/mol-model/structure/structure/util/boundary.ts
@@ -5,8 +5,6 @@
  */
 
 import Structure from '../structure'
-import ElementSet from '../element/set'
-import { ElementGroup } from '../../structure'
 import { Box3D, Sphere3D } from 'mol-math/geometry';
 import { Vec3 } from 'mol-math/linear-algebra';
 
@@ -14,19 +12,19 @@ function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D
     const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
     const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
 
-    const { units, elements } = s;
+    const { units } = s;
 
     let cx = 0, cy = 0, cz = 0;
     let radiusSq = 0;
     let size = 0;
 
-    for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
-        const group = ElementSet.groupAt(elements, i);
-        const { x, y, z } = units[ElementSet.groupUnitIndex(elements, i)];
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        const { x, y, z } = units[i].conformation;
 
-        size += ElementGroup.size(group);
-        for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
-            const e = ElementGroup.getAt(group, j);
+        const elements = units[i].elements;
+        size += elements.length;
+        for (let j = 0, _j = elements.length; j < _j; j++) {
+            const e = elements[j];
             const xx = x(e), yy = y(e), zz = z(e);
 
             min[0] = Math.min(xx, min[0]);
@@ -48,13 +46,12 @@ function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D
         cz /= size;
     }
 
-    for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
-        const group = ElementSet.groupAt(elements, i);
-        const { x, y, z } = units[ElementSet.groupUnitIndex(elements, i)];
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        const { x, y, z } = units[i].conformation;
 
-        size += ElementGroup.size(group);
-        for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
-            const e = ElementGroup.getAt(group, j);
+        const elements = units[i].elements;
+        for (let j = 0, _j = elements.length; j < _j; j++) {
+            const e = elements[j];
             const dx = x(e) - cx, dy = y(e) - cy, dz = z(e) - cz;
             const d = dx * dx + dy * dy + dz * dz;
             if (d > radiusSq) radiusSq = d;
diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts
index bb7230600205a39c78beeff7ac044b2d99a95d8d..8c8fc57e39404b9bddbbae63702aa06cc6f31e36 100644
--- a/src/mol-model/structure/structure/util/lookup3d.ts
+++ b/src/mol-model/structure/structure/util/lookup3d.ts
@@ -6,99 +6,99 @@
 
 import Structure from '../structure'
 import Element from '../element'
-import { Lookup3D, GridLookup3D, Result, Box3D, Sphere3D } from 'mol-math/geometry';
-import { ElementSet, Unit } from '../../structure';
-import { Vec3 } from 'mol-math/linear-algebra';
-import { OrderedSet } from 'mol-data/int';
-import { computeStructureBoundary } from './boundary';
+import { Lookup3D, /*GridLookup3D,*/ Result, Box3D, Sphere3D } from 'mol-math/geometry';
+//import { ElementSet, Unit } from '../../structure';
+//import { Vec3 } from 'mol-math/linear-algebra';
+//import { OrderedSet } from 'mol-data/int';
+//import { computeStructureBoundary } from './boundary';
 
-interface ElementSetLookup3D extends Lookup3D<Element> {}
+interface StructureLookup3D extends Lookup3D<Element> {}
 
-namespace ElementSetLookup3D {
-    class Impl implements ElementSetLookup3D {
-        private unitLookup: Lookup3D;
+namespace StructureLookup3D {
+    class Impl implements StructureLookup3D {
+        //private unitLookup: Lookup3D;
         private result = Result.create<Element>();
-        private pivot = Vec3.zero();
+        //private pivot = Vec3.zero();
 
         find(x: number, y: number, z: number, radius: number): Result<Element> {
             Result.reset(this.result);
-            const { units, elements } = this.structure;
-            const closeUnits = this.unitLookup.find(x, y, z, radius);
-            if (closeUnits.count === 0) return this.result;
-
-            for (let t = 0, _t = closeUnits.count; t < _t; t++) {
-                const i = closeUnits.indices[t];
-                const unitId = ElementSet.groupUnitIndex(elements, i);
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[unitId];
-                Vec3.set(this.pivot, x, y, z);
-                if (!unit.operator.isIdentity) {
-                    Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
-                }
-                const groupLookup = Unit.getLookup3d(unit, group);
-                const groupResult = groupLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
-                for (let j = 0, _j = groupResult.count; j < _j; j++) {
-                    Result.add(this.result, Element.create(unitId, groupResult.indices[j]), groupResult.squaredDistances[j]);
-                }
-            }
+            // const { units, elements } = this.structure;
+            // const closeUnits = this.unitLookup.find(x, y, z, radius);
+            // if (closeUnits.count === 0) return this.result;
+
+            // for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            //     const i = closeUnits.indices[t];
+            //     const unitId = ElementSet.groupUnitIndex(elements, i);
+            //     const group = ElementSet.groupAt(elements, i);
+            //     const unit = units[unitId];
+            //     Vec3.set(this.pivot, x, y, z);
+            //     if (!unit.operator.isIdentity) {
+            //         Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
+            //     }
+            //     const groupLookup = Unit.getLookup3d(unit, group);
+            //     const groupResult = groupLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
+            //     for (let j = 0, _j = groupResult.count; j < _j; j++) {
+            //         Result.add(this.result, Element.create(unitId, groupResult.indices[j]), groupResult.squaredDistances[j]);
+            //     }
+            // }
 
             return this.result;
         }
 
         check(x: number, y: number, z: number, radius: number): boolean {
-            const { units, elements } = this.structure;
-            const closeUnits = this.unitLookup.find(x, y, z, radius);
-            if (closeUnits.count === 0) return false;
-
-            for (let t = 0, _t = closeUnits.count; t < _t; t++) {
-                const i = closeUnits.indices[t];
-                const unitId = ElementSet.groupUnitIndex(elements, i);
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[unitId];
-                Vec3.set(this.pivot, x, y, z);
-                if (!unit.operator.isIdentity) {
-                    Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
-                }
-                const groupLookup = Unit.getLookup3d(unit, group);
-                if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
-            }
+            // const { units, elements } = this.structure;
+            // const closeUnits = this.unitLookup.find(x, y, z, radius);
+            // if (closeUnits.count === 0) return false;
+
+            // for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            //     const i = closeUnits.indices[t];
+            //     const unitId = ElementSet.groupUnitIndex(elements, i);
+            //     const group = ElementSet.groupAt(elements, i);
+            //     const unit = units[unitId];
+            //     Vec3.set(this.pivot, x, y, z);
+            //     if (!unit.operator.isIdentity) {
+            //         Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
+            //     }
+            //     const groupLookup = Unit.getLookup3d(unit, group);
+            //     if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
+            // }
 
             return false;
         }
 
         boundary: { box: Box3D; sphere: Sphere3D; };
 
-        constructor(private structure: Structure) {
-            const { units, elements } = structure;
-            const unitCount = ElementSet.groupCount(elements);
-            const xs = new Float32Array(unitCount);
-            const ys = new Float32Array(unitCount);
-            const zs = new Float32Array(unitCount);
-            const radius = new Float32Array(unitCount);
-
-            const center = Vec3.zero();
-            for (let i = 0; i < unitCount; i++) {
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[ElementSet.groupUnitIndex(elements, i)];
-                const lookup = Unit.getLookup3d(unit, group);
-                const s = lookup.boundary.sphere;
-
-                Vec3.transformMat4(center, s.center, unit.operator.matrix);
-
-                xs[i] = center[0];
-                ys[i] = center[1];
-                zs[i] = center[2];
-                radius[i] = s.radius;
-            }
-
-            this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
-            this.boundary = computeStructureBoundary(structure);
+        constructor(structure: Structure) {
+            // const { units, elements } = structure;
+            // const unitCount = ElementSet.groupCount(elements);
+            // const xs = new Float32Array(unitCount);
+            // const ys = new Float32Array(unitCount);
+            // const zs = new Float32Array(unitCount);
+            // const radius = new Float32Array(unitCount);
+
+            // const center = Vec3.zero();
+            // for (let i = 0; i < unitCount; i++) {
+            //     const group = ElementSet.groupAt(elements, i);
+            //     const unit = units[ElementSet.groupUnitIndex(elements, i)];
+            //     const lookup = Unit.getLookup3d(unit, group);
+            //     const s = lookup.boundary.sphere;
+
+            //     Vec3.transformMat4(center, s.center, unit.operator.matrix);
+
+            //     xs[i] = center[0];
+            //     ys[i] = center[1];
+            //     zs[i] = center[2];
+            //     radius[i] = s.radius;
+            // }
+
+            // this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
+            // this.boundary = computeStructureBoundary(structure);
         }
     }
 
-    export function create(s: Structure): ElementSetLookup3D {
+    export function create(s: Structure): StructureLookup3D {
         return new Impl(s);
     }
 }
 
-export { ElementSetLookup3D }
\ No newline at end of file
+export { StructureLookup3D }
\ No newline at end of file
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 03f04a2333d8fcfcc8af2b31f9b2b7cc02036255..65608d08b1f7812b80e3387900899d95650192c9 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -11,12 +11,12 @@ import * as fs from 'fs'
 import fetch from 'node-fetch'
 import CIF from 'mol-io/reader/cif'
 
-import { Structure, Model, Queries as Q, Element, ElementGroup, ElementSet, Selection, Symmetry, Unit, Query } from 'mol-model/structure'
-import { Segmentation, OrderedSet } from 'mol-data/int'
+import { Structure, Model, Queries as Q, Element, Selection, Symmetry, Query } from 'mol-model/structure'
+//import { Segmentation, OrderedSet } from 'mol-data/int'
 
 import to_mmCIF from 'mol-model/structure/export/mmcif'
 import { Run } from 'mol-task';
-import { EquivalenceClasses } from 'mol-data/util';
+//import { EquivalenceClasses } from 'mol-data/util';
 
 require('util.promisify').shim();
 const readFileAsync = util.promisify(fs.readFile);
@@ -119,19 +119,14 @@ export namespace PropertyAccess {
     }
 
     function sumProperty(structure: Structure, p: Element.Property<number>) {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
         const l = Element.Location();
-
         let s = 0;
 
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            l.unit = units[unitIds[i]];
-            const set = ElementSet.groupAt(elements, i);
-
-
-            for (let j = 0, _j = ElementGroup.size(set); j < _j; j++) {
-                l.element= ElementGroup.getAt(set, j);
+        for (const unit of structure.units) {
+            l.unit = unit;
+            const elements = unit.elements;
+            for (let j = 0, _j = elements.length; j < _j; j++) {
+                l.element = elements[j];
                 s += p(l);
             }
         }
@@ -139,44 +134,44 @@ export namespace PropertyAccess {
         return s;
     }
 
-    function sumPropertySegmented(structure: Structure, p: Element.Property<number>) {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
-        const l = Element.Location();
+    // function sumPropertySegmented(structure: Structure, p: Element.Property<number>) {
+    //     const { elements, units } = structure;
+    //     const unitIds = ElementSet.unitIndices(elements);
+    //     const l = Element.Location();
 
-        let s = 0;
+    //     let s = 0;
 
-        let vA = 0, cC = 0, rC = 0;
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unit = units[unitIds[i]] as Unit.Atomic;
-            l.unit = unit;
-            const set = ElementSet.groupAt(elements, i);
-
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.elements);
-            const residues = unit.hierarchy.residueSegments;
-            while (chainsIt.hasNext) {
-                cC++;
-
-                const chainSegment = chainsIt.move();
-                const residuesIt = Segmentation.transientSegments(residues, set.elements, chainSegment);
-                while (residuesIt.hasNext) {
-                    rC++;
-                    const residueSegment = residuesIt.move();
-                    // l.element= 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.element= ElementGroup.getAt(set, j);
-                        vA++;
-                        s += p(l);
-                    }
-                }
-            }
-        }
+    //     let vA = 0, cC = 0, rC = 0;
+    //     for (let i = 0, _i = unitIds.length; i < _i; i++) {
+    //         const unit = units[unitIds[i]] as Unit.Atomic;
+    //         l.unit = unit;
+    //         const set = ElementSet.groupAt(elements, i);
 
-        console.log('seg atom count', vA, cC, rC);
+    //         const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.elements);
+    //         const residues = unit.hierarchy.residueSegments;
+    //         while (chainsIt.hasNext) {
+    //             cC++;
 
-        return s;
-    }
+    //             const chainSegment = chainsIt.move();
+    //             const residuesIt = Segmentation.transientSegments(residues, set.elements, chainSegment);
+    //             while (residuesIt.hasNext) {
+    //                 rC++;
+    //                 const residueSegment = residuesIt.move();
+    //                 // l.element= 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.element= ElementGroup.getAt(set, j);
+    //                     vA++;
+    //                     s += p(l);
+    //                 }
+    //             }
+    //         }
+    //     }
+
+    //     console.log('seg atom count', vA, cC, rC);
+
+    //     return s;
+    // }
 
     // function sumPropertyResidue(structure: Structure, p: Element.Property<number>) {
     //     const { atoms, units } = structure;
@@ -199,20 +194,20 @@ export namespace PropertyAccess {
     //     return s;
     // }
 
-    function sumPropertyAtomSetIt(structure: Structure, p: Element.Property<number>) {
-        const { elements, units } = structure;
+    // function sumPropertyAtomSetIt(structure: Structure, p: Element.Property<number>) {
+    //     const { elements, units } = structure;
 
-        let s = 0;
-        const atomsIt = ElementSet.elements(elements);
-        const l = Element.Location();
-        while (atomsIt.hasNext) {
-            const a = atomsIt.move();
-            l.unit = units[Element.unit(a)];
-            l.element= Element.index(a);
-            s += p(l);
-        }
-        return s;
-    }
+    //     let s = 0;
+    //     const atomsIt = ElementSet.elements(elements);
+    //     const l = Element.Location();
+    //     while (atomsIt.hasNext) {
+    //         const a = atomsIt.move();
+    //         l.unit = units[Element.unit(a)];
+    //         l.element= Element.index(a);
+    //         s += p(l);
+    //     }
+    //     return s;
+    // }
 
     // function sumPropertySegmentedMutable(structure: Structure, p: Property<number>) {
     //     const { atoms, units } = structure;
@@ -302,24 +297,24 @@ export namespace PropertyAccess {
         console.log('exported');
     }
 
-    export async function testGrouping(structure: Structure) {
-        const { elements, units } = await Run(Symmetry.buildAssembly(structure, '1'));
-        console.log('grouping', units.length);
-        console.log('built asm');
+    // export async function testGrouping(structure: Structure) {
+    //     const { elements, units } = await Run(Symmetry.buildAssembly(structure, '1'));
+    //     console.log('grouping', units.length);
+    //     console.log('built asm');
 
-        const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
-            ({ unit, group }) => ElementGroup.hashCode(group),
-            (a, b) => a.unit.model.id === b.unit.model.id && (a.group.key === b.group.key && OrderedSet.areEqual(a.group.elements, b.group.elements))
-        );
+    //     const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
+    //         ({ unit, group }) => ElementGroup.hashCode(group),
+    //         (a, b) => a.unit.model.id === b.unit.model.id && (a.group.key === b.group.key && OrderedSet.areEqual(a.group.elements, b.group.elements))
+    //     );
 
-        for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
-            const group = ElementSet.groupAt(elements, i);
-            const unitId = ElementSet.groupUnitIndex(elements, i);
-            uniqueGroups.add(unitId, { unit: units[unitId], group });
-        }
+    //     for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
+    //         const group = ElementSet.groupAt(elements, i);
+    //         const unitId = ElementSet.groupUnitIndex(elements, i);
+    //         uniqueGroups.add(unitId, { unit: units[unitId], group });
+    //     }
 
-        console.log('group count', uniqueGroups.groups.length);
-    }
+    //     console.log('group count', uniqueGroups.groups.length);
+    // }
 
     function query(q: Query, s: Structure) {
         return Run((q(s)));
@@ -330,17 +325,17 @@ export namespace PropertyAccess {
         // const { structures, models } = await getBcif('3j3q');
 
         const { structures, models /*, mmcif*/ } = await readCIF('e:/test/quick/1hrv_updated.cif');
-        const { structures: s1, /*, mmcif*/ } = await readCIF('e:/test/quick/1tqn_updated.cif');
+        //const { structures: s1, /*, mmcif*/ } = await readCIF('e:/test/quick/1tqn_updated.cif');
 
-        testGrouping(structures[0]);
-        console.log('------');
-        testGrouping(s1[0]);
+        // testGrouping(structures[0]);
+        // console.log('------');
+        // testGrouping(s1[0]);
         //const { structures, models/*, mmcif*/ } = await readCIF('e:/test/quick/5j7v_updated.cif');
 
         //console.log(mmcif.pdbx_struct_oper_list.matrix.toArray());
         // console.log(mmcif.pdbx_struct_oper_list.vector.toArray());
 
-        // testAssembly('5j7v', structures[0]);
+        await testAssembly('1hrv', structures[0]);
         // throw '';
 
         // console.log(models[0].symmetry.assemblies);
@@ -357,10 +352,10 @@ export namespace PropertyAccess {
 
         console.log('bs', baseline(models[0]));
         console.log('sp', sumProperty(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element)));
-        console.log(sumPropertySegmented(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element)));
+        //console.log(sumPropertySegmented(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element)));
 
         //console.log(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.element));
-        console.log(sumPropertyAtomSetIt(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element)));
+        //console.log(sumPropertyAtomSetIt(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element)));
         //console.log(sumProperty(structures[0], Property.cachedAtomColumn(m => m.conformation.atomId)));
         //console.log(sumDirect(structures[0]));
         //console.log('r', sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom])));
@@ -403,14 +398,14 @@ export namespace PropertyAccess {
         console.log(Selection.structureCount(q2r));
         //console.log(q1(structures[0]));
 
-        //const col = models[0].conformation.atomId.value;
+        const col = models[0].atomSiteConformation.atomId.value;
         const suite = new B.Suite();
         suite
             //.add('test q', () => q1(structures[0]))
             //.add('test q', () => q(structures[0]))
+            .add('test int', () => sumProperty(structures[0], l => col(l.element)))
             .add('test q1', async () => await q1(structures[0]))
             .add('test q3', async () => await q3(structures[0]))
-            //.add('test int', () => sumProperty(structures[0], l => col(l.element))
             // .add('sum residue', () => sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom])))
 
             // .add('baseline', () =>  baseline(models[0]))