diff --git a/data/mmcif-field-names.csv b/data/mmcif-field-names.csv
index 61649087a630b56eaacafc345338f2b8b37a9584..bd1f2d51bdfe8257abd257a27b09b9f4b6afc390 100644
--- a/data/mmcif-field-names.csv
+++ b/data/mmcif-field-names.csv
@@ -1,3 +1,7 @@
+atom_sites.entry_id
+atom_sites.fract_transf_matrix
+atom_sites.fract_transf_vector
+
 atom_site.group_PDB
 atom_site.id
 atom_site.type_symbol
diff --git a/package-lock.json b/package-lock.json
index 7e574f630d3b78f7f2f79cd0b910a5f38116cbf4..f8ca913d1861b34ca68229a9b8a821c70985b36f 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/src/apps/cif2bcif/converter.ts b/src/apps/cif2bcif/converter.ts
index 8a0530bc5a9acb1dc25e351ba017fbe066307a25..06a9ed517a6529752436d7a22ef9e4908abc80c0 100644
--- a/src/apps/cif2bcif/converter.ts
+++ b/src/apps/cif2bcif/converter.ts
@@ -9,11 +9,10 @@ import CIF, { CifCategory } from 'mol-io/reader/cif'
 import * as Encoder from 'mol-io/writer/cif'
 import * as fs from 'fs'
 import classify from './field-classifier'
-import { Run } from 'mol-task'
 
 async function getCIF(path: string) {
     const str = fs.readFileSync(path, 'utf8');
-    const parsed = await Run(CIF.parseText(str));
+    const parsed = await CIF.parseText(str).run();
     if (parsed.isError) {
         throw new Error(parsed.toString());
     }
diff --git a/src/apps/combine-mmcif/index.ts b/src/apps/combine-mmcif/index.ts
index d107f81132f4110c1c4d01152711ba7f8a9155c5..205c55c11c748d752dd12033405a524fd6102b22 100644
--- a/src/apps/combine-mmcif/index.ts
+++ b/src/apps/combine-mmcif/index.ts
@@ -13,7 +13,7 @@ require('util.promisify').shim();
 const readFile = util.promisify(fs.readFile);
 const writeFile = util.promisify(fs.writeFile);
 
-import { Run, Progress } from 'mol-task'
+import { Progress } from 'mol-task'
 import { Database, Table, DatabaseCollection } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
 // import { CCD_Schema } from 'mol-io/reader/cif/schema/ccd'
@@ -78,7 +78,7 @@ export async function getBIRD() {
 async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data);
     console.time('parse cif');
-    const parsed = await Run(comp, p => console.log(Progress.format(p)), 250);
+    const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
     console.timeEnd('parse cif');
     if (parsed.isError) throw parsed;
     return parsed
diff --git a/src/apps/schema-generator/schema-from-mmcif-dic.ts b/src/apps/schema-generator/schema-from-mmcif-dic.ts
index eb1d7e74ba4a336e5fc70860a753d1bf8a4ec78e..3d6fc0e4bd3bff518e7d61b3c59032de2096eebd 100644
--- a/src/apps/schema-generator/schema-from-mmcif-dic.ts
+++ b/src/apps/schema-generator/schema-from-mmcif-dic.ts
@@ -14,15 +14,14 @@ import CIF, { CifFrame } from 'mol-io/reader/cif'
 import { generateSchema } from './util/cif-dic'
 import { generate } from './util/generate'
 import { Filter } from './util/json-schema'
-import { Run } from 'mol-task'
 
 async function runGenerateSchema(name: string, fieldNamesPath?: string, typescript = false, out?: string) {
     await ensureMmcifDicAvailable()
-    const mmcifDic = await Run(CIF.parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')));
+    const mmcifDic = await CIF.parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
     if (mmcifDic.isError) throw mmcifDic
 
     await ensureIhmDicAvailable()
-    const ihmDic = await Run(CIF.parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')));
+    const ihmDic = await CIF.parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
     if (ihmDic.isError) throw ihmDic
 
     const mmcifDicVersion = CIF.schema.dic(mmcifDic.result.blocks[0]).dictionary.version.value(0)
@@ -44,7 +43,7 @@ async function runGenerateSchema(name: string, fieldNamesPath?: string, typescri
 
 async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
     const fieldNamesStr = fs.readFileSync(fieldNamesPath, 'utf8')
-    const parsed = await Run(Csv(fieldNamesStr, { noColumnNames: true }));
+    const parsed = await Csv(fieldNamesStr, { noColumnNames: true }).run();
     if (parsed.isError) throw parser.error
     const csvFile = parsed.result;
 
diff --git a/src/apps/structure-info/helpers.ts b/src/apps/structure-info/helpers.ts
index 94095a99055759cdbab8d9b2c677345632520848..f4a86d9a67ac824988e4fbe0fffd13ced8b2aac5 100644
--- a/src/apps/structure-info/helpers.ts
+++ b/src/apps/structure-info/helpers.ts
@@ -10,7 +10,7 @@ import fetch from 'node-fetch'
 require('util.promisify').shim();
 
 import CIF from 'mol-io/reader/cif'
-import { Run, Progress } from 'mol-task'
+import { Progress } from 'mol-task'
 
 const readFileAsync = util.promisify(fs.readFile);
 
@@ -27,7 +27,7 @@ async function readFile(path: string) {
 
 async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data);
-    const parsed = await Run(comp, p => console.log(Progress.format(p)), 250);
+    const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
     if (parsed.isError) throw parsed;
     return parsed.result;
 }
diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index 8ab4ad1dbf361bdda44c7d36492d95e616ca126a..13c6ed4422ff664de86e1eb801a6e224be21cb0e 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -10,13 +10,14 @@ require('util.promisify').shim();
 
 // import { Table } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
-import { Model, Structure, Element, ElementSet, Unit, ElementGroup, Queries } from 'mol-model/structure'
+import { Model, Structure, Element, Unit, Queries } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { Table } from 'mol-data/db';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
 import { openCif, downloadCif } from './helpers';
+import { BitFlags } from 'mol-util';
+import { SecondaryStructureType } from 'mol-model/structure/model/types';
 
 
 async function downloadFromPdb(pdb: string) {
@@ -31,7 +32,7 @@ async function readPdbFile(path: string) {
 }
 
 export function atomLabel(model: Model, aI: number) {
-    const { atoms, residues, chains, residueSegments, chainSegments } = model.hierarchy
+    const { atoms, residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
     const { label_atom_id } = atoms
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
@@ -40,16 +41,42 @@ export function atomLabel(model: Model, aI: number) {
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
 }
 
+export function residueLabel(model: Model, rI: number) {
+    const { residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { label_comp_id, label_seq_id } = residues
+    const { label_asym_id } = chains
+    const cI = chainSegments.segmentMap[residueSegments.segments[rI]]
+    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
+}
 
-export function printBonds(structure: Structure) {
-    const { units, elements } = structure;
-    const unitIds = ElementSet.unitIndices(elements);
+export function printSecStructure(model: Model) {
+    console.log('Secondary Structure\n=============');
+    const { residues } = model.atomicHierarchy;
+    const { type, key } = model.properties.secondaryStructure;
+
+    const count = residues._rowCount;
+    let rI = 0;
+    while (rI < count) {
+        let start = rI;
+        while (rI < count && key[start] === key[rI]) rI++;
+        rI--;
+
+        if (BitFlags.has(type[start], SecondaryStructureType.Flag.Beta)) {
+            console.log(`Sheet: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
+        } else if (BitFlags.has(type[start], SecondaryStructureType.Flag.Helix)) {
+            console.log(`Helix: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
+        }
 
-    for (let i = 0, _i = OrderedSet.size(unitIds); i < _i; i++) {
-        const unit = units[OrderedSet.getAt(unitIds, i)];
-        const group = ElementSet.groupFromUnitIndex(elements, OrderedSet.getAt(unitIds, i));
+        rI++;
+    }
+}
+
+export function printBonds(structure: Structure) {
+    for (const unit of structure.units) {
+        if (!Unit.isAtomic(unit)) continue;
 
-        const { count, offset, neighbor } = Unit.getGroupBonds(unit, group);
+        const elements = unit.elements;
+        const { count, offset, neighbor } = unit.bonds;
         const { model }  = unit;
 
         if (!count) continue;
@@ -60,9 +87,9 @@ export function printBonds(structure: Structure) {
 
             if (end <= start) continue;
 
-            const aI = ElementGroup.getAt(group, j);
+            const aI = elements[j];
             for (let _bI = start; _bI < end; _bI++) {
-                const bI = ElementGroup.getAt(group, neighbor[_bI])
+                const bI = elements[neighbor[_bI]];
                 console.log(`${atomLabel(model, aI)} -- ${atomLabel(model, bI)}`);
             }
         }
@@ -84,26 +111,23 @@ export function printSequence(model: Model) {
 
 export function printUnits(structure: Structure) {
     console.log('Units\n=============');
-    const { elements, units } = structure;
-    const unitIds = ElementSet.unitIndices(elements);
     const l = Element.Location();
 
-    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;
-        const size = OrderedSet.size(set);
+    for (const unit of structure.units) {
+        l.unit = unit;
+        const elements = unit.elements;
+        const size = OrderedSet.size(elements);
 
         if (Unit.isAtomic(l.unit)) {
-            console.log(`Atomic unit ${unitId}: ${size} elements`);
+            console.log(`Atomic unit ${unit.id} ${unit.conformation.operator.name}: ${size} elements`);
         } else if (Unit.isCoarse(l.unit)) {
-            console.log(`Coarse unit ${unitId} (${l.unit.elementType === CoarseGrained.ElementType.Sphere ? 'spheres' : 'gaussians'}): ${size} elements.`);
+            console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
 
-            const props = Queries.props.coarse_grained;
+            const props = Queries.props.coarse;
             const seq = l.unit.model.sequence;
 
-            for (let j = 0, _j = Math.min(size, 10); j < _j; j++) {
-                l.element = OrderedSet.getAt(set, j);
+            for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
+                l.element = OrderedSet.getAt(elements, j);
 
                 const residues: string[] = [];
                 const start = props.seq_id_begin(l), end = props.seq_id_end(l);
@@ -111,24 +135,26 @@ export function printUnits(structure: Structure) {
                 for (let e = start; e <= end; e++) residues.push(compId(e));
                 console.log(`${props.asym_id(l)}:${start}-${end} (${residues.join('-')}) ${props.asym_id(l)} [${props.x(l).toFixed(2)}, ${props.y(l).toFixed(2)}, ${props.z(l).toFixed(2)}]`);
             }
-            if (size > 10) console.log(`...`);
+            if (size > 3) console.log(`...`);
         }
     }
 }
 
 
 export function printIHMModels(model: Model) {
-    if (!model.coarseGrained.isDefined) return false;
+    if (!model.coarseHierarchy.isDefined) return false;
     console.log('IHM Models\n=============');
-    console.log(Table.formatToString(model.coarseGrained.modelList));
+    console.log(Table.formatToString(model.coarseHierarchy.models));
 }
 
 async function run(mmcif: mmCIF_Database) {
-    const models = Model.create({ kind: 'mmCIF', data: mmcif });
+    const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
     const structure = Structure.ofModel(models[0]);
     printSequence(models[0]);
     printIHMModels(models[0]);
     printUnits(structure);
+    // printBonds(structure);
+    printSecStructure(models[0]);
 }
 
 async function runDL(pdb: string) {
diff --git a/src/apps/structure-info/volume.ts b/src/apps/structure-info/volume.ts
index 325e268dd7779e311408fe7c72280b6a7acac48c..00dd391711eba424686f5825594056426ac04380 100644
--- a/src/apps/structure-info/volume.ts
+++ b/src/apps/structure-info/volume.ts
@@ -12,7 +12,6 @@ import { VolumeData, parseDensityServerData, VolumeIsoValue } from 'mol-model/vo
 import { downloadCif } from './helpers'
 import CIF from 'mol-io/reader/cif'
 import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
-import { Run } from 'mol-task';
 import { Table } from 'mol-data/db';
 import { computeVolumeSurface } from 'mol-geo/representation/volume/surface';
 import { StringBuilder } from 'mol-util';
@@ -25,7 +24,7 @@ type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
 async function getVolume(url: string): Promise<Volume> {
     const cif = await downloadCif(url, true);
     const data = CIF.schema.densityServer(cif.blocks[1]);
-    return { source: data, volume: await Run(parseDensityServerData(data)) };
+    return { source: data, volume: await parseDensityServerData(data).run() };
 }
 
 function print(data: Volume) {
@@ -38,7 +37,7 @@ function print(data: Volume) {
 }
 
 async function doMesh(data: Volume, filename: string) {
-    const mesh = await Run(computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)));
+    const mesh = await computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)).run();
     console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
 
     // Export the mesh in OBJ format.
diff --git a/src/examples/task.ts b/src/examples/task.ts
index 41025f042c48eb1252f14483ba94e1f10c771470..64143cfb7da152845fa702b6854eb0c5f677c018 100644
--- a/src/examples/task.ts
+++ b/src/examples/task.ts
@@ -4,11 +4,11 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task, Run, Progress, Scheduler, now, MultistepTask, chunkedSubtask } from 'mol-task'
+import { Task, Progress, Scheduler, now, MultistepTask, chunkedSubtask } from 'mol-task'
 
 export async function test1() {
     const t = Task.create('test', async () => 1);
-    const r = await Run(t);
+    const r = await t.run();
     console.log(r);
 }
 
@@ -53,9 +53,9 @@ export function testTree() {
         if (ctx.shouldUpdate) await ctx.update('hi! 3');
 
         // ctx.update('Running children...', true);
-        const c1 = ctx.runChild(createTask(250, 1));
-        const c2 = ctx.runChild(createTask(500, 2));
-        const c3 = ctx.runChild(createTask(750, 3));
+        const c1 = createTask(250, 1).runAsChild(ctx);
+        const c2 = createTask(500, 2).runAsChild(ctx);
+        const c3 = createTask(750, 3).runAsChild(ctx);
 
         //await ctx.runChild(abortAfter(350));
 
@@ -87,8 +87,8 @@ export const ms = MultistepTask('ms-task', ['step 1', 'step 2', 'step 3'], async
         const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
         return s.i;
     });
-    
-    await ctx.runChild(child);
+
+    await child.runAsChild(ctx);
     await Scheduler.delay(250);
     await step(1);
     await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
@@ -114,7 +114,7 @@ async function test() {
         //const r = await Run(testTree(), abortingObserver, 250);
         //console.log(r);
 
-        const m = await Run(ms({ i: 10 }), logP);
+        const m = await ms({ i: 10 }).run(logP);
         console.log(m);
     } catch (e) {
         console.error(e);
diff --git a/src/mol-app/service/job.ts b/src/mol-app/service/job.ts
index ed2ec6a5da0a65538d2890c587d706f12e901687..3bfd0263ef88655bd885c945267c182efce00183 100644
--- a/src/mol-app/service/job.ts
+++ b/src/mol-app/service/job.ts
@@ -9,7 +9,7 @@ import { Context } from '../context/context'
 import { JobEvents } from '../event/basic';
 import { PerformanceMonitor } from 'mol-util/performance-monitor';
 import { formatProgress } from 'mol-util';
-import { Progress, Task, Run } from 'mol-task';
+import { Progress, Task } from 'mol-task';
 
 export class Job<T> {
     private info: Job.Info;
@@ -109,7 +109,7 @@ export namespace Job {
             JobEvents.Started.dispatch(this.context, this.info);
             this.context.performance.start('job' + this.info.id);
 
-            this.result = Run(this.task, (p: Progress) => this.progressUpdated(p), 250)
+            this.result = this.task.run((p: Progress) => this.progressUpdated(p), 250)
             this.result.then(() => this.resolved()).catch(e => this.rejected(e));
         }
 
diff --git a/src/mol-data/generic/unique-array.ts b/src/mol-data/generic/unique-array.ts
index 5a5caa501af2ba0ba4098c9cf0904ee8d9296859..dad6693959d1d47d7bf66a50f2456bbc0b341ee8 100644
--- a/src/mol-data/generic/unique-array.ts
+++ b/src/mol-data/generic/unique-array.ts
@@ -15,9 +15,10 @@ namespace UniqueArray {
     }
 
     export function add<K, T>({ keys, array }: UniqueArray<K, T>, key: K, value: T) {
-        if (keys.has(key)) return;
+        if (keys.has(key)) return false;
         keys.add(key);
         array[array.length] = value;
+        return true;
     }
 }
 
diff --git a/src/mol-data/int/_spec/sorted-array.spec.ts b/src/mol-data/int/_spec/sorted-array.spec.ts
index db8cd15d7c90b396bc1226635c34b3222982dcbb..2cd82af32ce9046bb8c2b7a3a71779ea68084071 100644
--- a/src/mol-data/int/_spec/sorted-array.spec.ts
+++ b/src/mol-data/int/_spec/sorted-array.spec.ts
@@ -16,6 +16,11 @@ describe('sortedArray', () => {
         it(name, () => expect(a).toEqual(b));
     }
 
+    function compareArrays(a: ArrayLike<number>, b: ArrayLike<number>) {
+        expect(a.length).toBe(b.length);
+        for (let i = 0; i < a.length; i++) expect(a[i]).toBe(b[i]);
+    }
+
     const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
     const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
 
@@ -48,6 +53,12 @@ describe('sortedArray', () => {
 
     testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1));
 
+    it('deduplicate', () => {
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 1, 1])), [1]);
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 2, 2, 3, 4])), [1, 2, 3, 4]);
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]);
+    });
+
     // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
     // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
 });
\ No newline at end of file
diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts
index f3147c7086664c7900c78db8c89f1e87b15ff509..4a5476c16c46de1194639194442ba6b904339a81 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]; }
@@ -267,6 +273,22 @@ export function subtract(a: Nums, b: Nums) {
     return ofSortedArray(indices);
 }
 
+export function deduplicate(xs: Nums) {
+    if (xs.length < 2) return xs;
+    let count = 1;
+    for (let i = 0, _i = xs.length - 1; i < _i; i++) {
+        if (xs[i] !== xs[i + 1]) count++;
+    }
+    if (count === xs.length) return xs;
+    const ret = new Int32Array(count);
+    let o = 0;
+    for (let i = 0, _i = xs.length - 1; i < _i; i++) {
+        if (xs[i] !== xs[i + 1]) ret[o++] = xs[i];
+    }
+    ret[o] = xs[xs.length - 1];
+    return ret;
+}
+
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
 function getSuitableIntersectionRange(a: Nums, b: Nums) {
diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts
index 08235061224bfebfdf5f019281936e5c090bb65b..928e383ddf1a083f9a652428131074e4d5a8c735 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;
@@ -36,6 +40,8 @@ namespace SortedArray {
     export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any;
     export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
     export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any;
+
+    export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any;
 }
 
 interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' }
diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts
index e40456499bcc6315d01d6ce17bca83ccd243323a..cfad84f041f3daeffaedb9b340dde3e3801a8d52 100644
--- a/src/mol-data/util/equivalence-classes.ts
+++ b/src/mol-data/util/equivalence-classes.ts
@@ -17,6 +17,7 @@ class EquivalenceClassesImpl<K, V> {
         return { id, keys, value };
     }
 
+    // Return the group representative.
     add(key: K, a: V) {
         const hash = this.getHash(a);
         if (this.byHash.has(hash)) {
@@ -25,16 +26,16 @@ class EquivalenceClassesImpl<K, V> {
                 const group = groups[i];
                 if (this.areEqual(a, group.value)) {
                     group.keys[group.keys.length] = key;
-                    return group.id;
+                    return group.value;
                 }
             }
             const group = this.createGroup(key, a);
             groups[groups.length] = group;
-            return group.id;
+            return group.value;
         } else {
             const group = this.createGroup(key, a);
             this.byHash.set(hash, [group]);
-            return group.id;
+            return group.value;
         }
     }
 
diff --git a/src/mol-data/util/hash-functions.ts b/src/mol-data/util/hash-functions.ts
index 00cae92c4d6f16ba47ed7281b5a773d432198164..8584baa5946e9a06a4ef1ca8e07b8e43607028b9 100644
--- a/src/mol-data/util/hash-functions.ts
+++ b/src/mol-data/util/hash-functions.ts
@@ -45,6 +45,14 @@ export function hash4(i: number, j: number, k: number, l: number) {
     return a;
 }
 
+export function hashString(s: string) {
+    let h = 0;
+    for (let i = 0, l = s.length; i < l; i++) {
+        h = (h << 5) - h + s.charCodeAt(i++) | 0;
+    }
+    return h;
+}
+
 /**
  * A unique number for each pair of integers
  * Biggest representable pair is (67108863, 67108863) (limit imposed by Number.MAX_SAFE_INTEGER)
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-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts
index 274da6a077bbfdeedcf8449020d710e27647cfe0..363e1771ed7ff49ddb907967511e6123f4769410 100644
--- a/src/mol-geo/representation/structure/index.ts
+++ b/src/mol-geo/representation/structure/index.ts
@@ -2,11 +2,10 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ElementGroup, ElementSet, Structure, Unit } from 'mol-model/structure';
-import { EquivalenceClasses } from 'mol-data/util';
-import { OrderedSet } from 'mol-data/int'
+import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/scene';
 import { Representation, RepresentationProps } from '..';
@@ -15,7 +14,7 @@ import { Representation, RepresentationProps } from '..';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
-    create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: P) => Task<void>
+    create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
 }
 
@@ -27,8 +26,7 @@ export interface StructureRepresentation<P extends RepresentationProps = {}> ext
 
 interface GroupRepresentation<T> {
     repr: UnitsRepresentation<T>
-    units: Unit[]
-    elementGroup: ElementGroup
+    group: Unit.SymmetryGroup
 }
 
 export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> {
@@ -39,40 +37,12 @@ export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P
         renderObjects,
         create(structure: Structure, props: P = {} as P) {
             return Task.create('StructureRepresentation.create', async ctx => {
-                const { elements, units } = structure;
-                const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
-                    ({ unit, group }) => ElementGroup.hashCode(group),
-                    (a, b) => a.unit.model.id === b.unit.model.id && OrderedSet.areEqual(a.group.elements, b.group.elements)
-                );
-
-                // const uniqueTransformations = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
-                //     ({ unit, group }) => unit.operator.matrix.join(','),
-                //     (a, b) => Mat4.areEqual(a.unit.operator.matrix, b.unit.operator.matrix, EPSILON.Value)
-                // );
-
-                const unitIndices = ElementSet.unitIndices(elements);
-                for (let i = 0, _i = unitIndices.length; i < _i; i++) {
-                    const unitIndex = unitIndices[i];
-                    const group = ElementSet.groupFromUnitIndex(elements, unitIndex);
-                    const unit = units[unitIndex]
-                    uniqueGroups.add(unitIndex, { unit, group });
-                    // uniqueTransformations.add(unitIndex, { unit, group });
-                }
-
-                // console.log({ uniqueGroups, uniqueTransformations })
-
-                for (let i = 0, il = uniqueGroups.groups.length; i < il; i++) {
-                    const groupUnits: Unit[] = []
-                    const group = uniqueGroups.groups[i]
-                    // console.log('group', i)
-                    for (let j = 0, jl = group.length; j < jl; j++) {
-                        groupUnits.push(units[group[j]])
-                    }
-                    const elementGroup = ElementSet.groupFromUnitIndex(elements, group[0])
+                const groups = StructureSymmetry.getTransformGroups(structure);
+                for (let i = 0; i < groups.length; i++) {
+                    const group = groups[i];
                     const repr = reprCtor()
-                    groupReprs.push({ repr, units: groupUnits, elementGroup })
-                    await ctx.update({ message: 'Building structure unit representations...', current: i, max: il });
-                    await ctx.runChild(repr.create(groupUnits, elementGroup, props));
+                    groupReprs.push({ repr, group })
+                    await repr.create(group, props).runAsChild(ctx, { message: 'Building structure unit representations...', current: i, max: groups.length });
                     renderObjects.push(...repr.renderObjects)
                 }
             });
@@ -83,10 +53,10 @@ export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P
                 renderObjects.length = 0 // clear
                 for (let i = 0, il = groupReprs.length; i < il; ++i) {
                     const groupRepr = groupReprs[i]
-                    const { repr, units, elementGroup } = groupRepr
-                    await ctx.update({ message: 'Updating structure unit representations...', current: i, max: il });
-                    if (!await ctx.runChild(repr.update(props))) {
-                        await ctx.runChild(repr.create(units, elementGroup, props))
+                    const { repr, group } = groupRepr
+                    const state = { message: 'Updating structure unit representations...', current: i, max: il };
+                    if (!await repr.update(props).runAsChild(ctx, state)) {
+                        await repr.create(group, props).runAsChild(ctx, state)
                     }
                     renderObjects.push(...repr.renderObjects)
                 }
diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts
index 71f317e39c3a31e83cce6d0f63a3f25201406e29..5828e518c5fe5f325a0fcdf477c28ef83150eea9 100644
--- a/src/mol-geo/representation/structure/point.ts
+++ b/src/mol-geo/representation/structure/point.ts
@@ -2,12 +2,12 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { ValueCell } from 'mol-util/value-cell'
 import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
-import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup, Element } from 'mol-model/structure';
+import { Unit, Element } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
@@ -16,6 +16,7 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { createTransforms, createColors, createSizes } from './utils';
 import { deepEqual } from 'mol-util';
+import { SortedArray } from 'mol-data/int';
 
 export const DefaultPointProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
@@ -25,16 +26,17 @@ export const DefaultPointProps = {
 }
 export type PointProps = Partial<typeof DefaultPointProps>
 
-export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
-    const elementCount = OrderedSet.size(elementGroup.elements)
+export function createPointVertices(unit: Unit) {
+    const elements = unit.elements
+    const elementCount = elements.length
     const vertices = new Float32Array(elementCount * 3)
 
-    const { x, y, z } = unit
+    const { x, y, z } = unit.conformation
     const l = Element.Location()
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
-        l.element = ElementGroup.getAt(elementGroup, i)
+        l.element = elements[i];
         const i3 = i * 3
         vertices[i3] = x(l.element)
         vertices[i3 + 1] = y(l.element)
@@ -49,21 +51,21 @@ export default function Point(): UnitsRepresentation<PointProps> {
     let curProps = DefaultPointProps
 
     let _units: ReadonlyArray<Unit>
-    let _elementGroup: ElementGroup
+    let _elements: SortedArray
 
     return {
         renderObjects,
-        create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) {
+        create(group: Unit.SymmetryGroup, props: PointProps = {}) {
             return Task.create('Point.create', async ctx => {
                 renderObjects.length = 0 // clear
                 curProps = { ...DefaultPointProps, ...props }
 
-                _units = units
-                _elementGroup = elementGroup
+                _units = group.units
+                _elements = group.elements;
 
                 const { colorTheme, sizeTheme, alpha, visible } = curProps
-                const elementCount = OrderedSet.size(elementGroup.elements)
-                const unitCount = units.length
+                const elementCount = _elements.length
+                const unitCount = _units.length
 
                 const vertexMap = VertexMap.create(
                     elementCount,
@@ -73,16 +75,16 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 )
 
                 await ctx.update('Computing point vertices');
-                const vertices = createPointVertices(units[0], elementGroup)
+                const vertices = createPointVertices(_units[0])
 
                 await ctx.update('Computing point transforms');
-                const transforms = createTransforms(units)
+                const transforms = createTransforms(group)
 
                 await ctx.update('Computing point colors');
-                const color = createColors(units, elementGroup, vertexMap, colorTheme)
+                const color = createColors(group, vertexMap, colorTheme)
 
                 await ctx.update('Computing point sizes');
-                const size = createSizes(units, elementGroup, vertexMap, sizeTheme)
+                const size = createSizes(group, vertexMap, sizeTheme)
 
                 points = createPointRenderObject({
                     objectId: 0,
@@ -106,7 +108,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
         },
         update(props: PointProps) {
             return Task.create('Point.update', async ctx => {
-                if (!points || !_units || !_elementGroup) return false
+                if (!points || !_units || !_elements) return false
 
                 const newProps = { ...curProps, ...props }
                 if (deepEqual(curProps, newProps)) {
diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts
index 689f2bfc9bfe0bfac81346a6294d5ae2330e2620..7f2b7ab722ffe1911b2cb70cc8de332bb98fb8b1 100644
--- a/src/mol-geo/representation/structure/spacefill.ts
+++ b/src/mol-geo/representation/structure/spacefill.ts
@@ -2,6 +2,7 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { ValueCell } from 'mol-util/value-cell'
@@ -9,15 +10,13 @@ import { ValueCell } from 'mol-util/value-cell'
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene'
 // import { createColorTexture } from 'mol-gl/util';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
-import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup, Element, Queries } from 'mol-model/structure';
+import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation } from './index';
 import { Task } from 'mol-task'
 import { MeshBuilder } from '../../shape/mesh-builder';
 import { createTransforms, createColors } from './utils';
 import { ColorTheme } from '../../theme';
 import VertexMap from '../../shape/vertex-map';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
 import { icosahedronVertexCount } from '../../primitive/icosahedron';
 
 export const DefaultSpacefillProps = {
@@ -29,17 +28,18 @@ export const DefaultSpacefillProps = {
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
-function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: number) {
+function createSpacefillMesh(unit: Unit, detail: number) {
     return Task.create('Sphere mesh', async ctx => {
-        const elementCount = OrderedSet.size(elementGroup.elements)
+        const { elements } = unit;
+        const elementCount = elements.length;
         const vertexCount = elementCount * icosahedronVertexCount(detail)
         const meshBuilder = MeshBuilder.create(vertexCount)
 
         let radius: Element.Property<number>
         if (Unit.isAtomic(unit)) {
             radius = Queries.props.atom.vdw_radius
-        } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
-            radius = Queries.props.coarse_grained.sphere_radius
+        } else if (Unit.isSpheres(unit)) {
+            radius = Queries.props.coarse.sphere_radius
         } else {
             console.warn('Unsupported unit type')
             return meshBuilder.getMesh()
@@ -48,12 +48,12 @@ function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: num
         const v = Vec3.zero()
         const m = Mat4.identity()
 
-        const { x, y, z } = unit
+        const { x, y, z } = unit.conformation
         const l = Element.Location()
         l.unit = unit
 
         for (let i = 0; i < elementCount; i++) {
-            l.element = ElementGroup.getAt(elementGroup, i)
+            l.element = elements[i]
             v[0] = x(l.element)
             v[1] = y(l.element)
             v[2] = z(l.element)
@@ -77,23 +77,22 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
 
     return {
         renderObjects,
-        create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: SpacefillProps = {}) {
+        create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
             return Task.create('Spacefill.create', async ctx => {
                 renderObjects.length = 0 // clear
 
                 const { detail, colorTheme, alpha, visible, doubleSided } = { ...DefaultSpacefillProps, ...props }
 
-                await ctx.update('Computing spacefill mesh');
-                const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail))
+                const mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
                 // console.log(mesh)
 
                 const vertexMap = VertexMap.fromMesh(mesh)
 
                 await ctx.update('Computing spacefill transforms');
-                const transforms = createTransforms(units)
+                const transforms = createTransforms(group)
 
                 await ctx.update('Computing spacefill colors');
-                const color = createColors(units, elementGroup, vertexMap, colorTheme)
+                const color = createColors(group, vertexMap, colorTheme)
 
                 spheres = createMeshRenderObject({
                     objectId: 0,
@@ -108,9 +107,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                     transform: ValueCell.create(transforms),
                     index: mesh.indexBuffer,
 
-                    instanceCount: units.length,
+                    instanceCount: group.units.length,
                     indexCount: mesh.triangleCount,
-                    elementCount: OrderedSet.size(elementGroup.elements),
+                    elementCount: group.elements.length,
                     positionCount: mesh.vertexCount
                 })
                 renderObjects.push(spheres)
diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts
index fba67c536e46e9447fd19b170299d25771b19996..c7cd81d17c60d684d5c7bb485dc91d25825c6bba 100644
--- a/src/mol-geo/representation/structure/utils.ts
+++ b/src/mol-geo/representation/structure/utils.ts
@@ -2,9 +2,10 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Unit, ElementGroup } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import { Mat4 } from 'mol-math/linear-algebra'
 
 import { createUniformColor } from '../../util/color-data';
@@ -14,35 +15,35 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 
-export function createTransforms(units: ReadonlyArray<Unit>) {
+export function createTransforms({ units }: Unit.SymmetryGroup) {
     const unitCount = units.length
     const transforms = new Float32Array(unitCount * 16)
     for (let i = 0; i < unitCount; i++) {
-        Mat4.toArray(units[i].operator.matrix, transforms, i * 16)
+        Mat4.toArray(units[i].conformation.operator.matrix, transforms, i * 16)
     }
     return transforms
 }
 
-export function createColors(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: ColorTheme) {
+export function createColors(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: ColorTheme) {
     switch (props.name) {
         case 'atom-index':
-            return elementIndexColorData({ units, elementGroup, vertexMap })
+            return elementIndexColorData({ group, vertexMap })
         case 'chain-id':
-            return chainIdColorData({ units, elementGroup, vertexMap })
+            return chainIdColorData({ group, vertexMap })
         case 'element-symbol':
-            return elementSymbolColorData({ units, elementGroup, vertexMap })
+            return elementSymbolColorData({ group, vertexMap })
         case 'instance-index':
-            return instanceIndexColorData({ units, elementGroup, vertexMap })
+            return instanceIndexColorData({ group, vertexMap })
         case 'uniform':
             return createUniformColor(props)
     }
 }
 
-export function createSizes(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: SizeTheme) {
+export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme) {
     switch (props.name) {
         case 'uniform':
             return createUniformSize(props)
         case 'vdw':
-            return elementSizeData({ units, elementGroup, vertexMap })
+            return elementSizeData({ group, vertexMap })
     }
 }
\ No newline at end of file
diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts
index 84aee724fff635612391ba5d63db2f2df2e8f55e..779c05940b96e083a7e308479f76d010deaa7e79 100644
--- a/src/mol-geo/representation/volume/index.ts
+++ b/src/mol-geo/representation/volume/index.ts
@@ -29,8 +29,7 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat
         create(volumeData: VolumeData, props: P = {} as P) {
             return Task.create('VolumeRepresentation.create', async ctx => {
                 const repr = reprCtor()
-                await ctx.update({ message: 'Building volume representation...', current: 0, max: 1 });
-                await ctx.runChild(repr.create(volumeData, props));
+                await repr.create(volumeData, props).runAsChild(ctx, { message: 'Building volume representation...', current: 0, max: 1 });
                 renderObjects.push(...repr.renderObjects)
             });
         },
diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts
index a2de1a61d10cef902ee848ee9efb6ddab6572ead..bc9531ead7527fed500969d8bf22aa1a966c2c9f 100644
--- a/src/mol-geo/representation/volume/surface.ts
+++ b/src/mol-geo/representation/volume/surface.ts
@@ -20,10 +20,10 @@ export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValu
     return Task.create<Mesh>('Volume Surface', async ctx => {
         ctx.update({ message: 'Marching cubes...' });
 
-        const mesh = await ctx.runChild(computeMarchingCubes({
+        const mesh = await computeMarchingCubes({
             isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
             scalarField: volume.data
-        }));
+        }).runAsChild(ctx);
 
         const transform = VolumeData.getGridToCartesianTransform(volume);
         ctx.update({ message: 'Transforming mesh...' });
@@ -56,7 +56,7 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
                 curProps = { ...DefaultSurfaceProps, ...props }
                 const { alpha, visible, flatShaded, flipSided, doubleSided } = curProps
 
-                const mesh = await ctx.runChild(computeVolumeSurface(volume, curProps.isoValue))
+                const mesh = await computeVolumeSurface(volume, curProps.isoValue).runAsChild(ctx)
                 if (!flatShaded) {
                     Mesh.computeNormalsImmediate(mesh)
                 }
diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts
index 43dfc0db654e236eaf9af30e8ed49b16d033d5be..d521d6036c9f12edaa560e8ba87be14b9bc76fb6 100644
--- a/src/mol-geo/theme/structure/color/chain-id.ts
+++ b/src/mol-geo/theme/structure/color/chain-id.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit, Queries, Element } from 'mol-model/structure';
+import { Unit, Queries, Element } from 'mol-model/structure';
 
 import { StructureColorDataProps } from '.';
 import { createAttributeOrElementColor } from '../../../util/color-data';
@@ -18,11 +18,11 @@ function createChainIdMap(unit: Unit) {
     let count: number
     let asym_id: Column<string>
     if (Unit.isAtomic(unit)) {
-        asym_id = unit.hierarchy.chains.label_asym_id
-        count = unit.hierarchy.chains._rowCount
+        asym_id = unit.model.atomicHierarchy.chains.label_asym_id
+        count = unit.model.atomicHierarchy.chains._rowCount
     } else if (Unit.isCoarse(unit)) {
-        asym_id = unit.siteBases.asym_id
-        count = unit.siteBases.count
+        asym_id = unit.coarseElements.asym_id
+        count = unit.coarseElements.count
     } else {
         console.warn('Unknown unit type')
         return { map, count: index }
@@ -39,7 +39,7 @@ function createChainIdMap(unit: Unit) {
 }
 
 export function chainIdColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
+    const { group: { units, elements }, vertexMap } = props
     const unit = units[0]
 
     const { map, count } = createChainIdMap(unit)
@@ -51,7 +51,7 @@ export function chainIdColorData(props: StructureColorDataProps) {
     if (Unit.isAtomic(unit)) {
         asym_id = Queries.props.chain.label_asym_id
     } else if (Unit.isCoarse(unit)) {
-        asym_id = Queries.props.coarse_grained.asym_id
+        asym_id = Queries.props.coarse.asym_id
     }
 
     const l = Element.Location()
@@ -59,7 +59,7 @@ export function chainIdColorData(props: StructureColorDataProps) {
 
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
-            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            l.element = elements[elementIdx]
             return scale.color(map.get(asym_id(l)) || 0)
         },
         vertexMap
diff --git a/src/mol-geo/theme/structure/color/element-index.ts b/src/mol-geo/theme/structure/color/element-index.ts
index b205043d46ffb5a1fee65485dc9f026644f7a2c6..f6540f95e3fff159b13f9f20908bba27152d83ec 100644
--- a/src/mol-geo/theme/structure/color/element-index.ts
+++ b/src/mol-geo/theme/structure/color/element-index.ts
@@ -6,13 +6,12 @@
 
 import { ColorScale } from 'mol-util/color';
 import { StructureColorDataProps } from '.';
-import { OrderedSet } from 'mol-data/int';
 import { createElementInstanceColor } from '../../../util/color-data';
 
 export function elementIndexColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
+    const { group: { units, elements }, vertexMap } = props
     const instanceCount = units.length
-    const elementCount = OrderedSet.size(elementGroup.elements)
+    const elementCount = elements.length
 
     const domain = [ 0, instanceCount * elementCount - 1 ]
     const scale = ColorScale.create({ domain })
diff --git a/src/mol-geo/theme/structure/color/element-symbol.ts b/src/mol-geo/theme/structure/color/element-symbol.ts
index 432807d2bb4ee75947a70538afc9f93844591b97..affedfac08a2ca695894d76ff1177370a827b2e8 100644
--- a/src/mol-geo/theme/structure/color/element-symbol.ts
+++ b/src/mol-geo/theme/structure/color/element-symbol.ts
@@ -7,7 +7,6 @@
 import { ElementSymbol } from 'mol-model/structure/model/types';
 import { Color } from 'mol-util/color';
 import { StructureColorDataProps } from '.';
-import { OrderedSet } from 'mol-data/int';
 import { createAttributeOrElementColor } from '../../../util/color-data';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
@@ -23,11 +22,11 @@ export function elementSymbolColor(element: ElementSymbol): Color {
 }
 
 export function elementSymbolColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
-    const { type_symbol } = units[0].model.hierarchy.atoms
+    const { group: { units, elements }, vertexMap } = props
+    const { type_symbol } = units[0].model.atomicHierarchy.atoms
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
-            const e = OrderedSet.getAt(elementGroup.elements, elementIdx)
+            const e = elements[elementIdx]
             return elementSymbolColor(type_symbol.value(e))
         },
         vertexMap
diff --git a/src/mol-geo/theme/structure/color/index.ts b/src/mol-geo/theme/structure/color/index.ts
index 82d5b22a6c6535744d2c1000f5d0948bdace8311..9e0d0318dc439923c33f6f164e660357520e8b0a 100644
--- a/src/mol-geo/theme/structure/color/index.ts
+++ b/src/mol-geo/theme/structure/color/index.ts
@@ -4,12 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureColorDataProps {
-    units: ReadonlyArray<Unit>,
-    elementGroup: ElementGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 
diff --git a/src/mol-geo/theme/structure/color/instance-index.ts b/src/mol-geo/theme/structure/color/instance-index.ts
index bd7d6f5775d05bf415f9e4df822f054086c4ad1c..ffae3b0861016c0901dac0e591150e26f114ae4f 100644
--- a/src/mol-geo/theme/structure/color/instance-index.ts
+++ b/src/mol-geo/theme/structure/color/instance-index.ts
@@ -9,7 +9,7 @@ import { StructureColorDataProps } from '.';
 import { createInstanceColor } from '../../../util/color-data';
 
 export function instanceIndexColorData(props: StructureColorDataProps) {
-    const { units } = props
+    const { group: { units } } = props
     const instanceCount = units.length
 
     const domain = [ 0, instanceCount - 1 ]
diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/element.ts
index 699bc071a499ab303d3e40d48c3befb465248afe..741ba136f322274b9e72abee37168e56d481fb62 100644
--- a/src/mol-geo/theme/structure/size/element.ts
+++ b/src/mol-geo/theme/structure/size/element.ts
@@ -4,26 +4,26 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Element, Unit, Queries } from 'mol-model/structure';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
+import { Element, Unit, Queries } from 'mol-model/structure';
 import { StructureSizeDataProps } from '.';
 import { createAttributeSize } from '../../../util/size-data';
 
 /** Create attribute data with the size of an element, i.e. vdw for atoms and radius for coarse spheres */
 export function elementSizeData(props: StructureSizeDataProps) {
-    const { units, elementGroup, vertexMap } = props
-    const unit = units[0]
+    const { group, vertexMap } = props
+    const unit = group.units[0]
+    const elements = group.elements;
     let radius: Element.Property<number>
     if (Unit.isAtomic(unit)) {
         radius = Queries.props.atom.vdw_radius
-    } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
-        radius = Queries.props.coarse_grained.sphere_radius
+    } else if (Unit.isSpheres(unit)) {
+        radius = Queries.props.coarse.sphere_radius
     }
     const l = Element.Location()
     l.unit = unit
     return createAttributeSize({
         sizeFn: (elementIdx: number) => {
-            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            l.element = elements[elementIdx]
             return radius(l)
         },
         vertexMap
diff --git a/src/mol-geo/theme/structure/size/index.ts b/src/mol-geo/theme/structure/size/index.ts
index 22caed502febf7ab0dd05a25201fe1c960335f07..0c18ca93ee04603b9334f682790ff56e64b21591 100644
--- a/src/mol-geo/theme/structure/size/index.ts
+++ b/src/mol-geo/theme/structure/size/index.ts
@@ -4,12 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureSizeDataProps {
-    units: ReadonlyArray<Unit>,
-    elementGroup: ElementGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 
diff --git a/src/mol-io/reader/_spec/csv.spec.ts b/src/mol-io/reader/_spec/csv.spec.ts
index b074b5bc7d51a2df190180065852e4f7dec8c75d..0802bc7491fedc90c702ef6bc24d7fa653746895 100644
--- a/src/mol-io/reader/_spec/csv.spec.ts
+++ b/src/mol-io/reader/_spec/csv.spec.ts
@@ -5,7 +5,6 @@
  */
 
 import Csv from '../csv/parser'
-import { Run } from 'mol-task';
 
 const csvStringBasic = `StrCol,IntCol,FloatCol
 # comment
@@ -24,7 +23,7 @@ string2\t42\t2.44`
 
 describe('csv reader', () => {
     it('basic', async () => {
-        const parsed = await Run(Csv(csvStringBasic));
+        const parsed = await Csv(csvStringBasic).run();
         if (parsed.isError) return;
         const csvFile = parsed.result;
 
@@ -46,7 +45,7 @@ describe('csv reader', () => {
     });
 
     it('advanced', async () => {
-        const parsed = await Run(Csv(csvStringAdvanced));
+        const parsed = await Csv(csvStringAdvanced).run();
         if (parsed.isError) return;
         const csvFile = parsed.result;
 
@@ -63,7 +62,7 @@ describe('csv reader', () => {
     });
 
     it('tabs', async () => {
-        const parsed = await Run(Csv(tabString, { delimiter: '\t' }));
+        const parsed = await Csv(tabString, { delimiter: '\t' }).run();;
         if (parsed.isError) return;
         const csvFile = parsed.result;
 
diff --git a/src/mol-io/reader/_spec/gro.spec.ts b/src/mol-io/reader/_spec/gro.spec.ts
index 6254be8f9d45d287f643e2d50993e8b62eb11bbe..055e4e61b227a58a86c5a1b4b6317c996558ec98 100644
--- a/src/mol-io/reader/_spec/gro.spec.ts
+++ b/src/mol-io/reader/_spec/gro.spec.ts
@@ -6,7 +6,6 @@
  */
 
 import Gro from '../gro/parser'
-import { Run } from 'mol-task';
 
 const groString = `MD of 2 waters, t= 4.2
     6
@@ -27,7 +26,7 @@ const groStringHighPrecision = `Generated by trjconv : 2168 system t=  15.00000
 
 describe('gro reader', () => {
     it('basic', async () => {
-        const parsed = await Run(Gro(groString));
+        const parsed = await Gro(groString).run();
 
         if (parsed.isError) {
             console.log(parsed)
@@ -58,7 +57,7 @@ describe('gro reader', () => {
     });
 
     it('high precision', async () => {
-        const parsed = await Run(Gro(groStringHighPrecision));
+        const parsed = await Gro(groStringHighPrecision).run();
 
         if (parsed.isError) {
             console.log(parsed)
diff --git a/src/mol-io/reader/_spec/mol2.spec.ts b/src/mol-io/reader/_spec/mol2.spec.ts
index 43244780636af6fbcd75010522ba9405b75ca2ff..dc88b16043caf6777ca6571656f2c3cfab6eb38f 100644
--- a/src/mol-io/reader/_spec/mol2.spec.ts
+++ b/src/mol-io/reader/_spec/mol2.spec.ts
@@ -1,6 +1,5 @@
 
 import Mol2 from '../mol2/parser'
-import { Run } from 'mol-task';
 
 const Mol2String = `@<TRIPOS>MOLECULE
 5816
@@ -247,7 +246,7 @@ GASTEIGER
 
 describe('mol2 reader', () => {
     it('basic', async () => {
-        const parsed =  await Run(Mol2(Mol2String));
+        const parsed =  await Mol2(Mol2String).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }
@@ -298,7 +297,7 @@ describe('mol2 reader', () => {
     });
 
     it('multiblocks', async () => {
-        const parsed =  await Run(Mol2(Mol2StringMultiBlocks));
+        const parsed =  await Mol2(Mol2StringMultiBlocks).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }
@@ -349,7 +348,7 @@ describe('mol2 reader', () => {
     });
 
     it('minimal', async () => {
-        const parsed =  await Run(Mol2(Mol2StringMinimal));
+        const parsed =  await Mol2(Mol2StringMinimal).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }
diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts
index 373fa1ae8401ea1043707cebc355e0760cf321dc..8f67d33bfa2b3c7ff56c2358ba004197450eb8ca 100644
--- a/src/mol-io/reader/cif/schema/mmcif.ts
+++ b/src/mol-io/reader/cif/schema/mmcif.ts
@@ -21,6 +21,11 @@ const Vector = Schema.Vector;
 const List = Schema.List;
 
 export const mmCIF_Schema = {
+    atom_sites: {
+        entry_id: str,
+        fract_transf_matrix: Matrix(3, 3),
+        fract_transf_vector: Vector(3)
+    },
     atom_site: {
         auth_asym_id: str,
         auth_atom_id: str,
diff --git a/src/mol-math/geometry/spacegroup/construction.ts b/src/mol-math/geometry/spacegroup/construction.ts
index 65aa22e849283499982fbf6fefdd71ade328105b..2acdb5287eb62b96c24ee3a4617a5f742b04bc63 100644
--- a/src/mol-math/geometry/spacegroup/construction.ts
+++ b/src/mol-math/geometry/spacegroup/construction.ts
@@ -5,11 +5,12 @@
  */
 
 import { Vec3, Mat4 } from '../../linear-algebra'
-import { SpacegroupName, TransformData, GroupData, SpacegroupNumbers, SpacegroupNames, OperatorData } from './tables'
+import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNames } from './tables'
 import { SymmetryOperator } from '../../geometry';
 
 interface SpacegroupCell {
-    readonly number: number,
+    // zero based spacegroup number
+    readonly index: number,
     readonly size: Vec3,
     readonly anglesInRadians: Vec3,
     /** Transfrom cartesian -> fractional coordinates within the cell */
@@ -26,16 +27,18 @@ interface Spacegroup {
 
 namespace SpacegroupCell {
     // Create a 'P 1' with cellsize [1, 1, 1]
-    export function zero() {
-        return create(0, Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
-    }
+    export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
 
     // True if 'P 1' with cellsize [1, 1, 1]
     export function isZero(cell: SpacegroupCell) {
-        return cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
+        return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
     }
 
-    export function create(nameOrNumber: number | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell {
+    // returns Zero cell if the spacegroup does not exist
+    export function create(nameOrNumber: number | string | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell {
+        const index = getSpacegroupIndex(nameOrNumber);
+        if (index < 0) return Zero;
+
         const alpha = anglesInRadians[0];
         const beta = anglesInRadians[1];
         const gamma = anglesInRadians[2];
@@ -58,23 +61,18 @@ namespace SpacegroupCell {
         ]);
         const toFractional = Mat4.invert(Mat4.zero(), fromFractional)!;
 
-        const num = typeof nameOrNumber === 'number' ? nameOrNumber : SpacegroupNumbers[nameOrNumber];
-        return { number: num, size, anglesInRadians, toFractional, fromFractional };
+        return { index, size, anglesInRadians, toFractional, fromFractional };
     }
 }
 
-namespace Spacegroup {
-    export function create(nameOrNumber: number | SpacegroupName, cell: SpacegroupCell): Spacegroup {
-        const num = typeof nameOrNumber === 'number' ? nameOrNumber : SpacegroupNumbers[nameOrNumber];
-        const name = typeof nameOrNumber === 'number' ? SpacegroupNames[nameOrNumber] : nameOrNumber;
 
-        if (typeof num === 'undefined' || typeof name === 'undefined') {
-            throw new Error(`Spacegroup '${nameOrNumber}' is not defined.`);
-        }
-
-        const operators = GroupData[num].map(i => getOperatorMatrix(OperatorData[i]));
+namespace Spacegroup {
+    // P1 with [1, 1, 1] cell.
+    export const ZeroP1 = create(SpacegroupCell.Zero);
 
-        return { name, cell, operators };
+    export function create(cell: SpacegroupCell): Spacegroup {
+        const operators = GroupData[cell.index].map(i => getOperatorMatrix(OperatorData[i]));
+        return { name: SpacegroupNames[cell.index], cell, operators };
     }
 
     const _tempVec = Vec3.zero(), _tempMat = Mat4.zero();
diff --git a/src/mol-math/geometry/spacegroup/tables.ts b/src/mol-math/geometry/spacegroup/tables.ts
index de3ec97927b441f1fc80b0a8d665650b628bf331..91ae4bc0273ed99cc361e047c64684c0cacea90f 100644
--- a/src/mol-math/geometry/spacegroup/tables.ts
+++ b/src/mol-math/geometry/spacegroup/tables.ts
@@ -985,7 +985,7 @@ export const GroupData = [
     [0, 22, 57, 3, 159, 279, 654, 655, 158, 274, 656, 657, 29, 18, 25, 27, 284, 658, 262, 269, 280, 659, 257, 267],
 ];
 
-export const SpacegroupNumbers = {
+export const ZeroBasedSpacegroupNumbers = {
     'P 1': 0,
     'P -1': 1,
     'P 1 2 1': 2,
@@ -1333,12 +1333,19 @@ export const SpacegroupNumbers = {
     'I 2 3a': 265,
 };
 
-export type SpacegroupName = keyof typeof SpacegroupNumbers
+export type SpacegroupName = keyof typeof ZeroBasedSpacegroupNumbers
 
 export const SpacegroupNames: { [num: number]: SpacegroupName } = (function () {
     const names = Object.create(null);
-    for (const n of Object.keys(SpacegroupNumbers)) {
-        names[(SpacegroupNumbers as any)[n]] = n;
+    for (const n of Object.keys(ZeroBasedSpacegroupNumbers)) {
+        names[(ZeroBasedSpacegroupNumbers as any)[n]] = n;
     }
     return names;
-}());
\ No newline at end of file
+}());
+
+// return -1 if the spacegroup does not exist.
+export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number {
+    const index = typeof nameOrNumber === 'number' ? nameOrNumber - 1 : ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName];
+    if (typeof index === 'undefined' || typeof SpacegroupNames[index] === 'undefined') return -1;
+    return index;
+}
\ No newline at end of file
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-math/linear-algebra/3d/common.ts b/src/mol-math/linear-algebra/3d/common.ts
index eebaeb2901c60d1213d8e5963a5c44fb3fc2842f..4276dd8d60b1e254621bfb87e8dbd5302b023d0e 100644
--- a/src/mol-math/linear-algebra/3d/common.ts
+++ b/src/mol-math/linear-algebra/3d/common.ts
@@ -17,4 +17,8 @@
  * furnished to do so, subject to the following conditions:
  */
 
-export const enum EPSILON { Value = 0.000001 }
\ No newline at end of file
+export const enum EPSILON { Value = 0.000001 }
+
+export function equalEps(a: number, b: number, eps: number) {
+    return Math.abs(a - b) <= eps;
+}
diff --git a/src/mol-math/linear-algebra/3d/mat3.ts b/src/mol-math/linear-algebra/3d/mat3.ts
index cd577776b23b29e6abbd21049ae009ce42ce3a50..c72dc713dba2f2c51e76ae986e1f39474f09bdc5 100644
--- a/src/mol-math/linear-algebra/3d/mat3.ts
+++ b/src/mol-math/linear-algebra/3d/mat3.ts
@@ -104,6 +104,10 @@ namespace Mat3 {
         return Mat3.copy(Mat3.zero(), a);
     }
 
+    export function setValue(a: Mat3, i: number, j: number, value: number) {
+        a[3 * j + i] = value;
+    }
+
     /**
      * Copy the values from one Mat3 to another
      */
diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts
index 930eaf8518e425ff451b638830264510a8c5d97b..a23c5503950401af6c82307b0cbcbdce23bfbadd 100644
--- a/src/mol-math/linear-algebra/3d/mat4.ts
+++ b/src/mol-math/linear-algebra/3d/mat4.ts
@@ -17,7 +17,7 @@
  * furnished to do so, subject to the following conditions:
  */
 
-import { EPSILON } from './common'
+import { EPSILON, equalEps } from './common'
 import Vec3 from './vec3';
 import Quat from './quat';
 
@@ -539,11 +539,11 @@ namespace Mat4 {
             a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
             /* a30 = a[12], a31 = a[13], a32 = a[14],*/ a33 = a[15];
 
-        if (a33 !== 1 || a03 !== 0 || a13 !== 0 || a23 !== 0) {
+        if (!equalEps(a33, 1, eps) || !equalEps(a03, 0, eps) || !equalEps(a13, 0, eps) || !equalEps(a23, 0, eps)) {
             return false;
         }
         const det3x3 = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20);
-        if (det3x3 < 1 - eps || det3x3 > 1 + eps) {
+        if (!equalEps(det3x3, 1, eps)) {
             return false;
         }
         return true;
diff --git a/src/mol-math/linear-algebra/tensor.ts b/src/mol-math/linear-algebra/tensor.ts
index 976e410e1b0fb96055c435cabd873cb868791be9..4bcae46f7bd0db8987d0036ed8590ba804cd1d9c 100644
--- a/src/mol-math/linear-algebra/tensor.ts
+++ b/src/mol-math/linear-algebra/tensor.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Mat4, Vec3, Vec4 } from './3d'
+import { Mat4, Vec3, Vec4, Mat3 } from './3d'
 
 export interface Tensor { data: Tensor.Data, space: Tensor.Space }
 
@@ -65,6 +65,16 @@ export namespace Tensor {
         return mat;
     }
 
+    export function toMat3(space: Space, data: Tensor.Data): Mat3 {
+        if (space.rank !== 2) throw new Error('Invalid tensor rank');
+        const mat = Mat3.zero();
+        const d0 = Math.min(3, space.dimensions[0]), d1 = Math.min(3, space.dimensions[1]);
+        for (let i = 0; i < d0; i++) {
+            for (let j = 0; j < d1; j++) Mat3.setValue(mat, i, j, space.get(data, i, j));
+        }
+        return mat;
+    }
+
     export function toVec3(space: Space, data: Tensor.Data): Vec3 {
         if (space.rank !== 1) throw new Error('Invalid tensor rank');
         const vec = Vec3.zero();
diff --git a/src/mol-model/structure/_spec/atom-set.spec.ts b/src/mol-model/structure/_spec/atom-set.spec.ts
deleted file mode 100644
index 60b208053c028b902523853e1a01d0587c0b027c..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/_spec/atom-set.spec.ts
+++ /dev/null
@@ -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)]);
-    });
-});
\ No newline at end of file
diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index 3958a39faf09c85e7d07a2daca46530a396674df..d7f4491a64738c7009e719bd0672ce8d8741bb75 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'
 
@@ -96,7 +96,7 @@ const atom_site: Encoder.CategoryDefinition<Element.Location> = {
         str('auth_asym_id', P.chain.auth_asym_id),
 
         int('pdbx_PDB_model_num', P.unit.model_num),
-        str('pdbx_operator_name', P.unit.operator_name)
+        str('operator_name', P.unit.operator_name)
     ]
 };
 
@@ -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/model.ts b/src/mol-model/structure/model.ts
index c53fd0986639873923cf30fbceba2d6010233741..1704ab09282eb09ec1e30a442532e52dc80def63 100644
--- a/src/mol-model/structure/model.ts
+++ b/src/mol-model/structure/model.ts
@@ -7,6 +7,6 @@
 import Model from './model/model'
 import * as Types from './model/types'
 import Format from './model/format'
-import ModelSymmetry from './model/properties/symmetry'
+import { ModelSymmetry } from './model/properties/symmetry'
 
 export { Model, Types, Format, ModelSymmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts
index 59f96c2a79fd06f442d5a439196d3fe54576a9f6..2fa1f8ae1880d87643f6923b8a266c6693a151f5 100644
--- a/src/mol-model/structure/model/format.ts
+++ b/src/mol-model/structure/model/format.ts
@@ -4,15 +4,15 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { GroFile as GroFile } from 'mol-io/reader/gro/schema'
+// import { File as GroFile } from 'mol-io/reader/gro/schema'
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
 
 type Format =
-    | Format.gro
+    // | Format.gro
     | Format.mmCIF
 
 namespace Format {
-    export interface gro { kind: 'gro', data: GroFile }
+    // export interface gro { kind: 'gro', data: GroFile }
     export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database }
 }
 
diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts
index 6e365bb3506446712b08ab3573ea92d8d66fc934..1e04fb1e7e065d2c62174e0301d1b693d4f61bca 100644
--- a/src/mol-model/structure/model/formats/gro.ts
+++ b/src/mol-model/structure/model/formats/gro.ts
@@ -1,153 +1,154 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import UUID from 'mol-util/uuid'
-import { Column, Table } from 'mol-data/db'
-import { Interval, Segmentation } from 'mol-data/int'
-import { GroAtoms } from 'mol-io/reader/gro/schema'
-import Format from '../format'
-import Model from '../model'
-import * as Hierarchy from '../properties/hierarchy'
-import AtomSiteConformation from '../properties/atom-site-conformation'
-import CoarseGrained from '../properties/coarse-grained'
-import findHierarchyKeys from '../utils/hierarchy-keys'
-import { guessElement } from '../utils/guess-element'
-import { ElementSymbol} from '../types'
-import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-
-import gro_Format = Format.gro
-import Sequence from '../properties/sequence';
-import { Entities } from '../properties/common';
-
-type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
-
-function findHierarchyOffsets(atomsData: GroAtoms, bounds: Interval) {
-    const start = Interval.start(bounds), end = Interval.end(bounds);
-    const residues = [start], chains = [start];
-
-    const { residueName, residueNumber } = atomsData;
-
-    for (let i = start + 1; i < end; i++) {
-        const newResidue = !residueNumber.areValuesEqual(i - 1, i)
-            || !residueName.areValuesEqual(i - 1, i);
-        console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue)
-        if (newResidue) residues[residues.length] = i;
-    }
-    console.log(residues, residues.length)
-    return { residues, chains };
-}
-
-function guessElementSymbol (value: string) {
-    return ElementSymbol(guessElement(value));
-}
-
-function createHierarchyData(atomsData: GroAtoms, offsets: HierarchyOffsets): Hierarchy.Data {
-    console.log(atomsData.atomName)
-    const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
-        type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
-        label_atom_id: atomsData.atomName,
-        auth_atom_id: atomsData.atomName,
-        label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str),
-        pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
-    });
-
-    const residues = Table.view(Table.ofColumns(Hierarchy.ResiduesSchema, {
-        group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)),
-        label_comp_id: atomsData.residueName,
-        auth_comp_id: atomsData.residueName,
-        label_seq_id: atomsData.residueNumber,
-        auth_seq_id: atomsData.residueNumber,
-        pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str),
-    }), Hierarchy.ResiduesSchema, offsets.residues);
-    // Optimize the numeric columns
-    Table.columnToArray(residues, 'label_seq_id', Int32Array);
-    Table.columnToArray(residues, 'auth_seq_id', Int32Array);
-
-    // const chains = Table.ofColumns(Hierarchy.ChainsSchema, {
-    //     label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
-    //     auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
-    //     label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
-    // });
-
-    const chains = Table.ofUndefinedColumns(Hierarchy.ChainsSchema, 0);
-
-    return { atoms, residues, chains };
-}
-
-function getConformation(atoms: GroAtoms): AtomSiteConformation {
-    return {
-        id: UUID.create(),
-        atomId: atoms.atomNumber,
-        occupancy: Column.Undefined(atoms.count, Column.Schema.int),
-        B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float),
-        x: Column.mapToArray(atoms.x, x => x * 10, Float32Array),
-        y: Column.mapToArray(atoms.y, y => y * 10, Float32Array),
-        z: Column.mapToArray(atoms.z, z => z * 10, Float32Array)
-    }
-}
-
-function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) {
-    // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
-    return Table.areEqual(a.residues as Table<Hierarchy.ResiduesSchema>, b.residues as Table<Hierarchy.ResiduesSchema>)
-        && Table.areEqual(a.atoms as Table<Hierarchy.AtomsSchema>, b.atoms as Table<Hierarchy.AtomsSchema>)
-}
-
-function createModel(format: gro_Format, modelNum: number, previous?: Model): Model {
-    const structure = format.data.structures[modelNum];
-    const bounds = Interval.ofBounds(0, structure.atoms.count);
-
-    const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds);
-    const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets);
-
-    if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
-        return {
-            ...previous,
-            atomSiteConformation: getConformation(structure.atoms)
-        };
-    }
-
-    const hierarchySegments: Hierarchy.Segments = {
-        residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
-        chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
-    }
-
-    // TODO: create a better mock entity
-    const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{
-        id: '0',
-        src_method: 'syn',
-        type: 'polymer',
-        pdbx_number_of_molecules: 1
-    }]);
-
-    const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) };
-
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
-    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
-    return {
-        id: UUID.create(),
-        sourceData: format,
-        modelNum,
-        hierarchy,
-        entities,
-        sequence: Sequence.fromHierarchy(hierarchy),
-        atomSiteConformation: getConformation(structure.atoms),
-        coarseGrained: CoarseGrained.Empty,
-        symmetry: { assemblies: [] },
-        atomCount: structure.atoms.count
-    };
-}
-
-function buildModels(format: gro_Format): ReadonlyArray<Model> {
-    const models: Model[] = [];
-
-    format.data.structures.forEach((_, i) => {
-        const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0);
-        models.push(model);
-    });
-    return models;
-}
-
-export default buildModels;
\ No newline at end of file
+// TODO: make this work when the time comes.
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
+
+// import { Column, Table } from 'mol-data/db';
+// import { Interval, Segmentation } from 'mol-data/int';
+// import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif';
+// import { Atoms } from 'mol-io/reader/gro/schema';
+// import UUID from 'mol-util/uuid';
+// import Format from '../format';
+// import Model from '../model';
+// import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic';
+// import { CoarseHierarchy } from '../properties/coarse';
+// import { Entities } from '../properties/common';
+// import Sequence from '../properties/sequence';
+// import { ModelSymmetry } from '../properties/symmetry';
+// import { guessElement } from '../properties/utils/guess-element';
+// import { getAtomicKeys } from '../properties/utils/keys';
+// import { ElementSymbol } from '../types';
+
+// import gro_Format = Format.gro
+
+// type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
+
+// function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) {
+//     const start = Interval.start(bounds), end = Interval.end(bounds);
+//     const residues = [start], chains = [start];
+
+//     const { residueName, residueNumber } = atomsData;
+
+//     for (let i = start + 1; i < end; i++) {
+//         const newResidue = !residueNumber.areValuesEqual(i - 1, i)
+//             || !residueName.areValuesEqual(i - 1, i);
+//         console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue)
+//         if (newResidue) residues[residues.length] = i;
+//     }
+//     console.log(residues, residues.length)
+//     return { residues, chains };
+// }
+
+// function guessElementSymbol (value: string) {
+//     return ElementSymbol(guessElement(value));
+// }
+
+// function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicData {
+//     console.log(atomsData.atomName)
+//     const atoms = Table.ofColumns(AtomsSchema, {
+//         type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
+//         label_atom_id: atomsData.atomName,
+//         auth_atom_id: atomsData.atomName,
+//         label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str),
+//         pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
+//     });
+
+//     const residues = Table.view(Table.ofColumns(ResiduesSchema, {
+//         group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)),
+//         label_comp_id: atomsData.residueName,
+//         auth_comp_id: atomsData.residueName,
+//         label_seq_id: atomsData.residueNumber,
+//         auth_seq_id: atomsData.residueNumber,
+//         pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str),
+//     }), ResiduesSchema, offsets.residues);
+//     // Optimize the numeric columns
+//     Table.columnToArray(residues, 'label_seq_id', Int32Array);
+//     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
+
+//     // const chains = Table.ofColumns(Hierarchy.ChainsSchema, {
+//     //     label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+//     //     auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+//     //     label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
+//     // });
+
+//     const chains = Table.ofUndefinedColumns(ChainsSchema, 0);
+
+//     return { atoms, residues, chains };
+// }
+
+// function getConformation(atoms: Atoms): AtomicConformation {
+//     return {
+//         id: UUID.create(),
+//         atomId: atoms.atomNumber,
+//         occupancy: Column.Undefined(atoms.count, Column.Schema.int),
+//         B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float),
+//         x: Column.mapToArray(atoms.x, x => x * 10, Float32Array),
+//         y: Column.mapToArray(atoms.y, y => y * 10, Float32Array),
+//         z: Column.mapToArray(atoms.z, z => z * 10, Float32Array)
+//     }
+// }
+
+// function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
+//     // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
+//     return Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
+//         && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
+// }
+
+// function createModel(format: gro_Format, modelNum: number, previous?: Model): Model {
+//     const structure = format.data.structures[modelNum];
+//     const bounds = Interval.ofBounds(0, structure.atoms.count);
+
+//     const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds);
+//     const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets);
+
+//     if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
+//         return {
+//             ...previous,
+//             atomicConformation: getConformation(structure.atoms)
+//         };
+//     }
+
+//     const hierarchySegments: AtomicSegments = {
+//         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
+//         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
+//     }
+
+//     // TODO: create a better mock entity
+//     const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{
+//         id: '0',
+//         src_method: 'syn',
+//         type: 'polymer',
+//         pdbx_number_of_molecules: 1
+//     }]);
+
+//     const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) };
+
+//     const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);
+//     const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
+//     return {
+//         id: UUID.create(),
+//         sourceData: format,
+//         modelNum,
+//         atomicHierarchy,
+//         entities,
+//         sequence: Sequence.fromAtomicHierarchy(atomicHierarchy),
+//         atomicConformation: getConformation(structure.atoms),
+//         coarseHierarchy: CoarseHierarchy.Empty,
+//         coarseConformation: void 0 as any,
+//         symmetry: ModelSymmetry.Default
+//     };
+// }
+
+// function buildModels(format: gro_Format): ReadonlyArray<Model> {
+//     const models: Model[] = [];
+
+//     format.data.structures.forEach((_, i) => {
+//         const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0);
+//         models.push(model);
+//     });
+//     return models;
+// }
+
+// export default buildModels;
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index 33c09a598b84df5e8fb06d7ce4a4c2fca7c949bb..6f135c7209df51cbb0721bc969a8510205f0711e 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -4,22 +4,25 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import UUID from 'mol-util/uuid'
-import { Column, Table } from 'mol-data/db'
-import { Interval, Segmentation } from 'mol-data/int'
-import Format from '../format'
-import Model from '../model'
-import * as Hierarchy from '../properties/hierarchy'
-import AtomSiteConformation from '../properties/atom-site-conformation'
-import Symmetry from '../properties/symmetry'
-import findHierarchyKeys from '../utils/hierarchy-keys'
-import { ElementSymbol} from '../types'
-import createAssemblies from './mmcif/assembly'
+import { Column, Table } from 'mol-data/db';
+import { Interval, Segmentation } from 'mol-data/int';
+import { Spacegroup, SpacegroupCell } from 'mol-math/geometry';
+import { Vec3 } from 'mol-math/linear-algebra';
+import UUID from 'mol-util/uuid';
+import Format from '../format';
+import Model from '../model';
+import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic';
+import { Entities } from '../properties/common';
+import { ModelSymmetry } from '../properties/symmetry';
+import { getAtomicKeys } from '../properties/utils/atomic-keys';
+import { ElementSymbol } from '../types';
+import { createAssemblies } from './mmcif/assembly';
+import { getIHMCoarse } from './mmcif/ihm';
+import { getSequence } from './mmcif/sequence';
 
 import mmCIF_Format = Format.mmCIF
-import { getSequence } from './mmcif/sequence';
-import { Entities } from '../properties/common';
-import { coarseGrainedFromIHM } from './mmcif/ihm';
+import { Task } from 'mol-task';
+import { getSecondaryStructureMmCif } from './mmcif/secondary-structure';
 
 function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
@@ -51,25 +54,25 @@ function findHierarchyOffsets({ data }: mmCIF_Format, bounds: Interval) {
     return { residues, chains };
 }
 
-function createHierarchyData({ data }: mmCIF_Format, bounds: Interval, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): Hierarchy.Data {
+function createHierarchyData({ data }: mmCIF_Format, bounds: Interval, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData {
     const { atom_site } = data;
     const start = Interval.start(bounds), end = Interval.end(bounds);
-    const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
+    const atoms = Table.ofColumns(AtomsSchema, {
         type_symbol: Column.ofArray({ array: Column.mapToArray(Column.window(atom_site.type_symbol, start, end), ElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
         label_atom_id: Column.window(atom_site.label_atom_id, start, end),
         auth_atom_id: Column.window(atom_site.auth_atom_id, start, end),
         label_alt_id: Column.window(atom_site.label_alt_id, start, end),
         pdbx_formal_charge: Column.window(atom_site.pdbx_formal_charge, start, end)
     });
-    const residues = Table.view(atom_site, Hierarchy.ResiduesSchema, offsets.residues);
+    const residues = Table.view(atom_site, ResiduesSchema, offsets.residues);
     // Optimize the numeric columns
     Table.columnToArray(residues, 'label_seq_id', Int32Array);
     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
-    const chains = Table.view(atom_site, Hierarchy.ChainsSchema, offsets.chains);
+    const chains = Table.view(atom_site, ChainsSchema, offsets.chains);
     return { atoms, residues, chains };
 }
 
-function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomSiteConformation {
+function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomicConformation {
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const { atom_site } = data;
     return {
@@ -83,72 +86,100 @@ function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomSiteConf
     }
 }
 
-function getSymmetry(format: mmCIF_Format): Symmetry {
-    return { assemblies: createAssemblies(format) };
+function getSymmetry(format: mmCIF_Format): ModelSymmetry {
+    const assemblies = createAssemblies(format);
+    const spacegroup = getSpacegroup(format);
+    const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(format, spacegroup);
+    return { assemblies, spacegroup, isNonStandardCrytalFrame };
 }
 
-function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) {
+function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegroup) {
+    const { atom_sites } = format.data;
+    if (atom_sites._rowCount === 0) return false;
+    // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix
+    return false;
+}
+
+function getSpacegroup(format: mmCIF_Format): Spacegroup {
+    const { symmetry, cell } = format.data;
+    if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
+    const groupName = symmetry['space_group_name_H-M'].value(0);
+    const spaceCell = SpacegroupCell.create(groupName,
+        Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
+        Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
+
+    return Spacegroup.create(spaceCell);
+}
+
+function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
     // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
-    return Table.areEqual(a.chains as Table<Hierarchy.ChainsSchema>, b.chains as Table<Hierarchy.ChainsSchema>)
-        && Table.areEqual(a.residues as Table<Hierarchy.ResiduesSchema>, b.residues as Table<Hierarchy.ResiduesSchema>)
-        && Table.areEqual(a.atoms as Table<Hierarchy.AtomsSchema>, b.atoms as Table<Hierarchy.AtomsSchema>)
+    return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
+        && Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
+        && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
 }
 
 function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): Model {
     const hierarchyOffsets = findHierarchyOffsets(format, bounds);
     const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets);
 
-    if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
+    if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
         return {
             ...previous,
-            atomSiteConformation: getConformation(format, bounds)
+            atomicConformation: getConformation(format, bounds)
         };
     }
 
-    const hierarchySegments: Hierarchy.Segments = {
+    const hierarchySegments: AtomicSegments = {
         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
     }
 
     const entities: Entities = { data: format.data.entity, getEntityIndex: Column.createIndexer(format.data.entity.id) };
 
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
+    const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);
+
+    const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
 
-    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
+    const coarse = getIHMCoarse(format.data, entities);
 
     return {
         id: UUID.create(),
         sourceData: format,
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)),
         entities,
-        hierarchy,
-        sequence: getSequence(format.data, entities, hierarchy),
-        atomSiteConformation: getConformation(format, bounds),
-        coarseGrained: coarseGrainedFromIHM(format.data, entities),
-        symmetry: getSymmetry(format),
-        atomCount: Interval.size(bounds)
+        atomicHierarchy,
+        sequence: getSequence(format.data, entities, atomicHierarchy),
+        atomicConformation: getConformation(format, bounds),
+        coarseHierarchy: coarse.hierarchy,
+        coarseConformation: coarse.conformation,
+        properties: {
+            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy)
+        },
+        symmetry: getSymmetry(format)
     };
 }
 
-function buildModels(format: mmCIF_Format): ReadonlyArray<Model> {
-    const atomCount = format.data.atom_site._rowCount;
-    const isIHM = format.data.ihm_model_list._rowCount > 0;
-
-    if (atomCount === 0) {
-        return isIHM
-            ? [createModel(format, Interval.Empty, void 0)]
-            : [];
-    }
-
-    const models: Model[] = [];
-    let modelStart = 0;
-    while (modelStart < atomCount) {
-        const bounds = findModelBounds(format, modelStart);
-        const model = createModel(format, bounds, models.length > 0 ? models[models.length - 1] : void 0);
-        models.push(model);
-        modelStart = Interval.end(bounds);
-    }
-    return models;
+function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> {
+    return Task.create('Create mmCIF Model', async ctx => {
+        const atomCount = format.data.atom_site._rowCount;
+        const isIHM = format.data.ihm_model_list._rowCount > 0;
+
+        if (atomCount === 0) {
+            return isIHM
+                ? [createModel(format, Interval.Empty, void 0)]
+                : [];
+        }
+
+        const models: Model[] = [];
+        let modelStart = 0;
+        while (modelStart < atomCount) {
+            const bounds = findModelBounds(format, modelStart);
+            const model = createModel(format, bounds, models.length > 0 ? models[models.length - 1] : void 0);
+            models.push(model);
+            modelStart = Interval.end(bounds);
+        }
+        return models;
+    });
 }
 
 export default buildModels;
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts
index 2315133d4cb83c7219207a9690f5b55ca148db9b..48720596c26741364f2acc6306ae47ba87ad3426 100644
--- a/src/mol-model/structure/model/formats/mmcif/assembly.ts
+++ b/src/mol-model/structure/model/formats/mmcif/assembly.ts
@@ -12,7 +12,7 @@ import { Queries as Q, Query } from '../../../query'
 
 import mmCIF_Format = Format.mmCIF
 
-export default function create(format: mmCIF_Format): ReadonlyArray<Assembly> {
+export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> {
     const { pdbx_struct_assembly } = format.data;
     if (!pdbx_struct_assembly._rowCount) return [];
 
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts
index 139a7a8117647b2e14ff66dd0c9c5ea9f04bcbaf..95ac32d4d36ffcebcc35064cd69e5d5b790f1ba9 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds.ts
+++ b/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,
@@ -118,7 +118,7 @@ export namespace StructConn {
         const _p = (row: number, ps: typeof p1) => {
             if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
             const asymId = ps.label_asym_id.value(row)
-            const residueIndex = model.hierarchy.findResidueKey(
+            const residueIndex = model.atomicHierarchy.findResidueKey(
                 findEntityIdByAsymId(model, asymId),
                 ps.label_comp_id.value(row),
                 asymId,
@@ -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) {
diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts
index d8590657156c97e3f38c1137792dcca2825a06aa..1dfdc1c1d36a15658b7c88da88f7741382596ad0 100644
--- a/src/mol-model/structure/model/formats/mmcif/ihm.ts
+++ b/src/mol-model/structure/model/formats/mmcif/ihm.ts
@@ -5,50 +5,81 @@
  */
 
 import { mmCIF_Database as mmCIF, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
-import CoarseGrained from '../../properties/coarse-grained'
+import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../properties/coarse'
 import { Entities } from '../../properties/common';
 import { Column } from 'mol-data/db';
+import { getCoarseKeys } from '../../properties/utils/coarse-keys';
+import { UUID } from 'mol-util';
+import { Segmentation, Interval } from 'mol-data/int';
+import { Mat3, Tensor } from 'mol-math/linear-algebra';
 
-function coarseGrainedFromIHM(data: mmCIF, entities: Entities): CoarseGrained {
-    if (data.ihm_model_list._rowCount === 0) return CoarseGrained.Empty;
+export function getIHMCoarse(data: mmCIF, entities: Entities): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
+    if (data.ihm_model_list._rowCount === 0) return { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any };
 
     const { ihm_model_list, ihm_sphere_obj_site, ihm_gaussian_obj_site } = data;
     const modelIndex = Column.createIndexer(ihm_model_list.model_id);
 
+    const sphereData = getData(ihm_sphere_obj_site);
+    const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
+    const sphereKeys = getCoarseKeys(sphereData, modelIndex, entities);
+
+    const gaussianData = getData(ihm_gaussian_obj_site);
+    const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
+    const gaussianKeys = getCoarseKeys(gaussianData, modelIndex, entities);
+
     return {
-        isDefined: true,
-        modelList: ihm_model_list,
-        spheres: getSpheres(ihm_sphere_obj_site, entities, modelIndex),
-        gaussians: getGaussians(ihm_gaussian_obj_site, entities, modelIndex)
+        hierarchy: {
+            isDefined: true,
+            models: ihm_model_list,
+            spheres: { ...sphereData, ...sphereKeys },
+            gaussians: { ...gaussianData, ...gaussianKeys },
+        },
+        conformation: {
+            id: UUID.create(),
+            spheres: sphereConformation,
+            gaussians: gaussianConformation
+        }
     };
 }
 
-function getSpheres(data: mmCIF['ihm_sphere_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrained.Spheres {
-    const { Cartn_x, Cartn_y, Cartn_z, object_radius: radius, rmsf } = data;
-    const x = Cartn_x.toArray({ array: Float32Array });
-    const y = Cartn_y.toArray({ array: Float32Array });
-    const z = Cartn_z.toArray({ array: Float32Array });
-    return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, radius, rmsf };
+function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphereConformation {
+    return {
+        x: data.Cartn_x.toArray({ array: Float32Array }),
+        y: data.Cartn_y.toArray({ array: Float32Array }),
+        z: data.Cartn_z.toArray({ array: Float32Array }),
+        radius: data.object_radius.toArray({ array: Float32Array }),
+        rmsf: data.rmsf.toArray({ array: Float32Array })
+    };
 }
 
-function getGaussians(data: mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrained.Gaussians {
-    const { mean_Cartn_x, mean_Cartn_y, mean_Cartn_z, weight, covariance_matrix  } = data;
-    const x = mean_Cartn_x.toArray({ array: Float32Array });
-    const y = mean_Cartn_y.toArray({ array: Float32Array });
-    const z = mean_Cartn_z.toArray({ array: Float32Array });
-    return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, weight, covariance_matrix, matrix_space: mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space };
-}
+function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGaussianConformation {
+    const matrix_space = mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space;
+    const covariance_matrix: Mat3[] = [];
+    const { covariance_matrix: cm } = data;
 
-function getCommonColumns(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number) {
-    const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data;
+    for (let i = 0, _i = cm.rowCount; i < _i; i++) {
+        covariance_matrix[i] = Tensor.toMat3(matrix_space, cm.value(i));
+    }
 
     return {
-        entityKey: Column.mapToArray(entity_id, id => entities.getEntityIndex(id), Int32Array),
-        modelKey: Column.mapToArray(model_id, modelIndex, Int32Array),
-        asym_id,
-        seq_id_begin,
-        seq_id_end
+        x: data.mean_Cartn_x.toArray({ array: Float32Array }),
+        y: data.mean_Cartn_y.toArray({ array: Float32Array }),
+        z: data.mean_Cartn_z.toArray({ array: Float32Array }),
+        weight: data.weight.toArray({ array: Float32Array }),
+        covariance_matrix
     };
 }
 
-export { coarseGrainedFromIHM }
\ No newline at end of file
+function getChainSegments(asym_id: Column<string>) {
+    const offsets = [0];
+    for (let i = 1, _i = asym_id.rowCount; i < _i; i++) {
+        if (!asym_id.areValuesEqual(i - 1, i)) offsets[offsets.length] = i;
+    }
+
+    return Segmentation.ofOffsets(offsets, Interval.ofBounds(0, asym_id.rowCount));
+}
+
+function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData {
+    const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data;
+    return { count: model_id.rowCount, entity_id, model_id, asym_id, seq_id_begin, seq_id_end, chainSegments: getChainSegments(asym_id) };
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed07db38bf79d75bc18c3382989e5a2ca14e8796
--- /dev/null
+++ b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
@@ -0,0 +1,154 @@
+
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { mmCIF_Database as mmCIF, mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
+import { SecondaryStructureType } from '../../types';
+import { AtomicHierarchy } from '../../properties/atomic';
+import { SecondaryStructure } from '../../properties/seconday-structure';
+import { Column } from 'mol-data/db';
+
+export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
+    const map: SecondaryStructureMap = new Map();
+    addHelices(data.struct_conf, map);
+    // must add Helices 1st because of 'key' value assignment.
+    addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount);
+
+    const secStruct: SecondaryStructureData = {
+        type: new Int32Array(hierarchy.residues._rowCount) as any,
+        key: new Int32Array(hierarchy.residues._rowCount) as any
+    };
+
+    if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
+    return secStruct;
+}
+
+type SecondaryStructureEntry = {
+    startSeqNumber: number,
+    startInsCode: string | null,
+    endSeqNumber: number,
+    endInsCode: string | null,
+    type: SecondaryStructureType,
+    key: number
+}
+type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry>>
+type SecondaryStructureData = { type: SecondaryStructureType[], key: number[] }
+
+function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) {
+    if (!cat._rowCount) return;
+
+    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { pdbx_PDB_helix_class, conf_type_id } = cat;
+
+    for (let i = 0, _i = cat._rowCount; i < _i; i++) {
+        const type =  pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
+            ? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)]
+            : conf_type_id.valueKind(i) === Column.ValueKind.Present
+            ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)]
+            : SecondaryStructureType.Flag.NA
+
+        const entry: SecondaryStructureEntry = {
+            startSeqNumber: beg_label_seq_id.value(i),
+            startInsCode: pdbx_beg_PDB_ins_code.value(i),
+            endSeqNumber: end_label_seq_id.value(i),
+            endInsCode: pdbx_end_PDB_ins_code.value(i),
+            type: SecondaryStructureType.create(type),
+            key: i + 1
+        };
+
+        const asymId = beg_label_asym_id.value(i)!;
+        if (map.has(asymId)) {
+            map.get(asymId)!.set(entry.startSeqNumber, entry);
+        } else {
+            map.set(asymId, new Map([[entry.startSeqNumber, entry]]));
+        }
+    }
+}
+
+function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number) {
+    if (!cat._rowCount) return;
+
+    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { sheet_id } = cat;
+
+    const sheet_id_key = new Map<string, number>();
+    let currentKey = sheetCount + 1;
+
+    for (let i = 0, _i = cat._rowCount; i < _i; i++) {
+        const id = sheet_id.value(i);
+        let key: number;
+        if (sheet_id_key.has(id)) key = sheet_id_key.get(id)!;
+        else {
+            key = currentKey++;
+            sheet_id_key.set(id, key);
+        }
+
+        const entry: SecondaryStructureEntry = {
+            startSeqNumber: beg_label_seq_id.value(i),
+            startInsCode: pdbx_beg_PDB_ins_code.value(i),
+            endSeqNumber: end_label_seq_id.value(i),
+            endInsCode: pdbx_end_PDB_ins_code.value(i),
+            type: SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet),
+            key
+        };
+
+        const asymId = beg_label_asym_id.value(i)!;
+        if (map.has(asymId)) {
+            map.get(asymId)!.set(entry.startSeqNumber, entry);
+        } else {
+            map.set(asymId, new Map([[entry.startSeqNumber, entry]]));
+        }
+    }
+
+    return;
+}
+
+function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) {
+    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+    const { endSeqNumber, endInsCode, type, key } = entry;
+
+    let rI = resStart;
+    while (rI < resEnd) {
+        const seqNumber = label_seq_id.value(rI);
+
+        data.type[rI] = type;
+        data.key[rI] = key;
+
+        if ((seqNumber > endSeqNumber) ||
+            (seqNumber === endSeqNumber && pdbx_PDB_ins_code.value(rI) === endInsCode)) {
+            break;
+        }
+
+        rI++;
+    }
+}
+
+function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: SecondaryStructureMap, data: SecondaryStructureData) {
+    const { segments: chainSegments, count: chainCount } = hierarchy.chainSegments;
+    const { label_asym_id } = hierarchy.chains;
+    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+
+    for (let cI = 0; cI < chainCount; cI++) {
+        const resStart = chainSegments[cI], resEnd = chainSegments[cI + 1];
+        const asymId = label_asym_id.value(cI);
+
+        if (map.has(asymId)) {
+            const entries = map.get(asymId)!;
+
+            for (let rI = resStart; rI < resEnd; rI++) {
+                const seqNumber = label_seq_id.value(rI);
+                if (entries.has(seqNumber)) {
+                    const entry = entries.get(seqNumber)!;
+                    const insCode = pdbx_PDB_ins_code.value(rI);
+                    if (entry.startInsCode !== insCode) continue;
+                    assignSecondaryStructureEntry(hierarchy, entry, rI, resEnd, data);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model/structure/model/formats/mmcif/sequence.ts
index ccba6849b3719825cbe067a8d4eb5a7e45c2816e..3b30419696303332c6c91fec2fbc66b6de3f2451 100644
--- a/src/mol-model/structure/model/formats/mmcif/sequence.ts
+++ b/src/mol-model/structure/model/formats/mmcif/sequence.ts
@@ -7,11 +7,11 @@
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 import Sequence from '../../properties/sequence'
 import { Column } from 'mol-data/db';
-import { Hierarchy } from '../../properties/hierarchy';
+import { AtomicHierarchy } from '../../properties/atomic';
 import { Entities } from '../../properties/common';
 
-export function getSequence(cif: mmCIF, entities: Entities, hierarchy: Hierarchy): Sequence {
-    if (!cif.entity_poly_seq._rowCount) return Sequence.fromHierarchy(hierarchy);
+export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): Sequence {
+    if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(hierarchy);
 
     const { entity_id, num, mon_id } = cif.entity_poly_seq;
 
diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts
index a03d511f54dd47643b4cc8b2189fdb532e994e7e..1817e1c22868410ce6529934aa279e3096654189 100644
--- a/src/mol-model/structure/model/formats/mmcif/util.ts
+++ b/src/mol-model/structure/model/formats/mmcif/util.ts
@@ -16,9 +16,9 @@ export function findEntityIdByAsymId(model: Model, asymId: string) {
 }
 
 export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null) {
-    const { segmentMap, segments } = model.hierarchy.residueSegments
+    const { segmentMap, segments } = model.atomicHierarchy.residueSegments
     const idx = segmentMap[residueIndex]
-    const { label_atom_id, label_alt_id } = model.hierarchy.atoms;
+    const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms;
     for (let i = segments[idx], n = segments[idx + 1]; i <= n; ++i) {
         if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i;
     }
diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts
index e685af863b4467eeb50f4668607cff258db6c3f2..1053bde91c2382c05f3a64445ae12facec884b62 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -7,13 +7,13 @@
 import UUID from 'mol-util/uuid'
 import Format from './format'
 import Sequence from './properties/sequence'
-import Hierarchy from './properties/hierarchy'
-import AtomSiteConformation from './properties/atom-site-conformation'
-import Symmetry from './properties/symmetry'
-import CoarseGrained from './properties/coarse-grained'
+import { AtomicHierarchy, AtomicConformation } from './properties/atomic'
+import { ModelSymmetry } from './properties/symmetry'
+import { CoarseHierarchy, CoarseConformation } from './properties/coarse'
 import { Entities } from './properties/common';
+import { SecondaryStructure } from './properties/seconday-structure';
 
-import from_gro from './formats/gro'
+//import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
 
 /**
@@ -28,15 +28,17 @@ interface Model extends Readonly<{
 
     sourceData: Format,
 
+    symmetry: ModelSymmetry,
     entities: Entities,
     sequence: Sequence,
 
-    hierarchy: Hierarchy,
-    atomSiteConformation: AtomSiteConformation,
-    symmetry: Symmetry,
-    coarseGrained: CoarseGrained,
+    atomicHierarchy: AtomicHierarchy,
+    atomicConformation: AtomicConformation,
 
-    atomCount: number,
+    properties: { secondaryStructure: SecondaryStructure },
+
+    coarseHierarchy: CoarseHierarchy,
+    coarseConformation: CoarseConformation
 }> {
 
 } { }
@@ -44,22 +46,10 @@ interface Model extends Readonly<{
 namespace Model {
     export function create(format: Format) {
         switch (format.kind) {
-            case 'gro': return from_gro(format);
+            //case 'gro': return from_gro(format);
             case 'mmCIF': return from_mmCIF(format);
         }
     }
-    // export function spatialLookup(model: Model): GridLookup {
-    //     if (model['@spatialLookup']) return model['@spatialLookup']!;
-    //     const lookup = GridLookup(model.conformation);
-    //     model['@spatialLookup'] = lookup;
-    //     return lookup;
-    // }
-    // export function bonds(model: Model): Bonds {
-    //     if (model['@bonds']) return model['@bonds']!;
-    //     const bonds = computeBonds(model);
-    //     model['@bonds'] = bonds;
-    //     return bonds;
-    // }
 }
 
 export default Model
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/atomic.ts b/src/mol-model/structure/model/properties/atomic.ts
index 38550af272dffcbb155f37312f24310f75ab095f..65b7bb2cbbf97f583911af6c54409be3f3e89718 100644
--- a/src/mol-model/structure/model/properties/atomic.ts
+++ b/src/mol-model/structure/model/properties/atomic.ts
@@ -1,38 +1,9 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ElementSymbol } from '../types';
-
-export const AtomicNumbers: { [e: string]: number | undefined } = { 'H': 1, 'D': 1, 'T': 1, 'HE': 2, 'LI': 3, 'BE': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'NE': 10, 'NA': 11, 'MG': 12, 'AL': 13, 'SI': 14, 'P': 15, 'S': 16, 'CL': 17, 'AR': 18, 'K': 19, 'CA': 20, 'SC': 21, 'TI': 22, 'V': 23, 'CR': 24, 'MN': 25, 'FE': 26, 'CO': 27, 'NI': 28, 'CU': 29, 'ZN': 30, 'GA': 31, 'GE': 32, 'AS': 33, 'SE': 34, 'BR': 35, 'KR': 36, 'RB': 37, 'SR': 38, 'Y': 39, 'ZR': 40, 'NB': 41, 'MO': 42, 'TC': 43, 'RU': 44, 'RH': 45, 'PD': 46, 'AG': 47, 'CD': 48, 'IN': 49, 'SN': 50, 'SB': 51, 'TE': 52, 'I': 53, 'XE': 54, 'CS': 55, 'BA': 56, 'LA': 57, 'CE': 58, 'PR': 59, 'ND': 60, 'PM': 61, 'SM': 62, 'EU': 63, 'GD': 64, 'TB': 65, 'DY': 66, 'HO': 67, 'ER': 68, 'TM': 69, 'YB': 70, 'LU': 71, 'HF': 72, 'TA': 73, 'W': 74, 'RE': 75, 'OS': 76, 'IR': 77, 'PT': 78, 'AU': 79, 'HG': 80, 'TL': 81, 'PB': 82, 'BI': 83, 'PO': 84, 'AT': 85, 'RN': 86, 'FR': 87, 'RA': 88, 'AC': 89, 'TH': 90, 'PA': 91, 'U': 92, 'NP': 93, 'PU': 94, 'AM': 95, 'CM': 96, 'BK': 97, 'CF': 98, 'ES': 99, 'FM': 100, 'MD': 101, 'NO': 102, 'LR': 103, 'RF': 104, 'DB': 105, 'SG': 106, 'BH': 107, 'HS': 108, 'MT': 109 };
-
-// http://dx.doi.org/10.1021/jp8111556 (or 2.0)
-export const ElementVdwRadii: { [e: number]: number | undefined } = {
-  1: 1.1, 2: 1.4, 3: 1.81, 4: 1.53, 5: 1.92, 6: 1.7, 7: 1.55, 8: 1.52, 9: 1.47, 10: 1.54, 11: 2.27, 12: 1.73, 13: 1.84, 14: 2.1, 15: 1.8, 16: 1.8, 17: 1.75, 18: 1.88, 19: 2.75, 20: 2.31, 21: 2.3, 22: 2.15, 23: 2.05, 24: 2.05, 25: 2.05, 26: 2.05, 27: 2.0, 28: 2.0, 29: 2.0, 30: 2.1, 31: 1.87, 32: 2.11, 33: 1.85, 34: 1.9, 35: 1.83, 36: 2.02, 37: 3.03, 38: 2.49, 39: 2.4, 40: 2.3, 41: 2.15, 42: 2.1, 43: 2.05, 44: 2.05, 45: 2.0, 46: 2.05, 47: 2.1, 48: 2.2, 49: 2.2, 50: 1.93, 51: 2.17, 52: 2.06, 53: 1.98, 54: 2.16, 55: 3.43, 56: 2.68, 57: 2.5, 58: 2.48, 59: 2.47, 60: 2.45, 61: 2.43, 62: 2.42, 63: 2.4, 64: 2.38, 65: 2.37, 66: 2.35, 67: 2.33, 68: 2.32, 69: 2.3, 70: 2.28, 71: 2.27, 72: 2.25, 73: 2.2, 74: 2.1, 75: 2.05, 76: 2.0, 77: 2.0, 78: 2.05, 79: 2.1, 80: 2.05, 81: 1.96, 82: 2.02, 83: 2.07, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 2.4, 91: 2.0, 92: 2.3, 93: 2.0, 94: 2.0, 95: 2.0, 96: 2.0, 97: 2.0, 98: 2.0, 99: 2.0, 100: 2.0, 101: 2.0, 102: 2.0, 103: 2.0, 104: 2.0, 105: 2.0, 106: 2.0, 107: 2.0, 108: 2.0, 109: 2.0
-}
-
-// https://doi.org/10.1515/pac-2015-0305 (table 2, 3, and 4)
-export const ElementAtomWeights: { [e: number]: number | undefined } = {
-  1: 1.008, 2: 4.0026, 3: 6.94, 4: 9.0122, 5: 10.81, 6: 10.81, 7: 14.007, 8: 15.999, 9: 18.998, 10: 20.180, 11: 22.990, 12: 24.305, 13: 26.982, 14: 28.085, 15: 30.974, 16: 32.06, 17: 35.45, 18: 39.948, 19: 39.098, 20: 40.078, 21: 44.956, 22: 47.867, 23: 50.942, 24: 51.996, 25: 54.938, 26: 55.845, 27: 58.933, 28: 58.693, 29: 63.546, 30: 65.38, 31: 69.723, 32: 72.630, 33: 74.922, 34: 78.971, 35: 79.904, 36: 83.798, 37: 85.468, 38: 87.62, 39: 88.906, 40: 91.224, 41: 92.906, 42: 95.95, 43: 96.906, 44: 101.07, 45: 102.91, 46: 106.42, 47: 107.87, 48: 112.41, 49: 114.82, 50: 118.71, 51: 121.76, 52: 127.60, 53: 127.60, 54: 131.29, 55: 132.91, 56: 137.33, 57: 138.91, 58: 140.12, 59: 140.91, 60: 144.24, 61: 144.912, 62: 150.36, 63: 151.96, 64: 157.25, 65: 158.93, 66: 162.50, 67: 164.93, 68: 167.26, 69: 168.93, 70: 173.05, 71: 174.97, 72: 178.49, 73: 180.95, 74: 183.84, 75: 186.21, 76: 190.23, 77: 192.22, 78: 195.08, 79: 196.97, 80: 200.59, 81: 204.38, 82: 207.2, 83: 208.98, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 232.04, 91: 231.04, 92: 238.03, 93: 237.048, 94: 244.064, 95: 243.061, 96: 247.070, 97: 247.070, 98: 251.079, 99: 252.083, 100: 257.095, 101: 258.098, 102: 259.101, 103: 262.110, 104: 267.122, 105: 270.131, 106: 271.134, 107: 270.133, 108: 270.134, 109: 278.156
-}
-
-export const DefaultVdwRadius = 1.7;  // C
-export const DefaultAtomWeight = 10.81;  // C
-export const DefaultAtomNumber = 0;
-
-export function VdwRadius(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultVdwRadius : ElementVdwRadii[i]!
-}
-
-export function AtomWeight(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultAtomWeight : ElementAtomWeights[i]!
-}
-
-export function AtomNumber(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultAtomNumber : i
-}
\ No newline at end of file
+export * from './atomic/conformation'
+export * from './atomic/hierarchy'
+export * from './atomic/measures'
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/atom-site-conformation.ts b/src/mol-model/structure/model/properties/atomic/conformation.ts
similarity index 93%
rename from src/mol-model/structure/model/properties/atom-site-conformation.ts
rename to src/mol-model/structure/model/properties/atomic/conformation.ts
index ce94b8597d2d1920b469f86d3942898e403fa5de..fd99511a40461ec1ff316c5295cabb586382265f 100644
--- a/src/mol-model/structure/model/properties/atom-site-conformation.ts
+++ b/src/mol-model/structure/model/properties/atomic/conformation.ts
@@ -7,7 +7,7 @@
 import { Column } from 'mol-data/db'
 import UUID from 'mol-util/uuid'
 
-interface Conformation {
+export interface AtomicConformation {
     id: UUID,
 
     // ID is part of conformation because mmCIF is a leaky abstraction
@@ -23,6 +23,4 @@ interface Conformation {
     x: ArrayLike<number>,
     y: ArrayLike<number>,
     z: ArrayLike<number>
-}
-
-export default Conformation
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
similarity index 86%
rename from src/mol-model/structure/model/properties/hierarchy.ts
rename to src/mol-model/structure/model/properties/atomic/hierarchy.ts
index d25903d95d0e4bd865b1969152902deea071c966..fe50e990b242ece24021f6f8eee514388e95d6da 100644
--- a/src/mol-model/structure/model/properties/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -7,7 +7,7 @@
 import { Column, Table } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int'
 import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-import { ElementSymbol} from '../types'
+import { ElementSymbol} from '../../types'
 
 export const AtomsSchema = {
     type_symbol: Column.Schema.Aliased<ElementSymbol>(mmCIF.atom_site.type_symbol),
@@ -40,36 +40,34 @@ export const ChainsSchema = {
 export type ChainsSchema = typeof ChainsSchema
 export interface Chains extends Table<ChainsSchema> { }
 
-export interface Data {
+export interface AtomicData {
     atoms: Atoms,
     residues: Residues,
     chains: Chains
 }
 
-export interface Segments {
+export interface AtomicSegments {
     residueSegments: Segmentation,
     chainSegments: Segmentation
 }
 
-export interface Keys {
+export interface AtomicKeys {
     // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
     // are in increasing order.
     // monotonous sequences enable for example faster secondary structure assignment.
     isMonotonous: boolean,
 
     // assign a key to each residue index.
-    residueKey: Column<number>,
+    residueKey: ArrayLike<number>,
     // assign a key to each chain index
-    chainKey: Column<number>,
+    chainKey: ArrayLike<number>,
     // assigne a key to each chain index
     // also index to the Entities table.
-    entityKey: Column<number>,
+    entityKey: ArrayLike<number>,
 
-    findChainKey(entityId: string, label_asym_id: string): number,
+    findChainKey(entityId: string, label_asym_id: string): number
     findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
 }
 
-type _Hierarchy = Data & Segments & Keys
-export interface Hierarchy extends _Hierarchy { }
-
-export default Hierarchy
\ No newline at end of file
+type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys
+export interface AtomicHierarchy extends _Hierarchy { }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/atomic/measures.ts b/src/mol-model/structure/model/properties/atomic/measures.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05aad28a9c16dbb67ffc6b1b10e00b86527ca574
--- /dev/null
+++ b/src/mol-model/structure/model/properties/atomic/measures.ts
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ElementSymbol } from '../../types';
+
+export const AtomicNumbers: { [e: string]: number | undefined } = { 'H': 1, 'D': 1, 'T': 1, 'HE': 2, 'LI': 3, 'BE': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'NE': 10, 'NA': 11, 'MG': 12, 'AL': 13, 'SI': 14, 'P': 15, 'S': 16, 'CL': 17, 'AR': 18, 'K': 19, 'CA': 20, 'SC': 21, 'TI': 22, 'V': 23, 'CR': 24, 'MN': 25, 'FE': 26, 'CO': 27, 'NI': 28, 'CU': 29, 'ZN': 30, 'GA': 31, 'GE': 32, 'AS': 33, 'SE': 34, 'BR': 35, 'KR': 36, 'RB': 37, 'SR': 38, 'Y': 39, 'ZR': 40, 'NB': 41, 'MO': 42, 'TC': 43, 'RU': 44, 'RH': 45, 'PD': 46, 'AG': 47, 'CD': 48, 'IN': 49, 'SN': 50, 'SB': 51, 'TE': 52, 'I': 53, 'XE': 54, 'CS': 55, 'BA': 56, 'LA': 57, 'CE': 58, 'PR': 59, 'ND': 60, 'PM': 61, 'SM': 62, 'EU': 63, 'GD': 64, 'TB': 65, 'DY': 66, 'HO': 67, 'ER': 68, 'TM': 69, 'YB': 70, 'LU': 71, 'HF': 72, 'TA': 73, 'W': 74, 'RE': 75, 'OS': 76, 'IR': 77, 'PT': 78, 'AU': 79, 'HG': 80, 'TL': 81, 'PB': 82, 'BI': 83, 'PO': 84, 'AT': 85, 'RN': 86, 'FR': 87, 'RA': 88, 'AC': 89, 'TH': 90, 'PA': 91, 'U': 92, 'NP': 93, 'PU': 94, 'AM': 95, 'CM': 96, 'BK': 97, 'CF': 98, 'ES': 99, 'FM': 100, 'MD': 101, 'NO': 102, 'LR': 103, 'RF': 104, 'DB': 105, 'SG': 106, 'BH': 107, 'HS': 108, 'MT': 109 };
+
+// http://dx.doi.org/10.1021/jp8111556 (or 2.0)
+export const ElementVdwRadii: { [e: number]: number | undefined } = {
+  1: 1.1, 2: 1.4, 3: 1.81, 4: 1.53, 5: 1.92, 6: 1.7, 7: 1.55, 8: 1.52, 9: 1.47, 10: 1.54, 11: 2.27, 12: 1.73, 13: 1.84, 14: 2.1, 15: 1.8, 16: 1.8, 17: 1.75, 18: 1.88, 19: 2.75, 20: 2.31, 21: 2.3, 22: 2.15, 23: 2.05, 24: 2.05, 25: 2.05, 26: 2.05, 27: 2.0, 28: 2.0, 29: 2.0, 30: 2.1, 31: 1.87, 32: 2.11, 33: 1.85, 34: 1.9, 35: 1.83, 36: 2.02, 37: 3.03, 38: 2.49, 39: 2.4, 40: 2.3, 41: 2.15, 42: 2.1, 43: 2.05, 44: 2.05, 45: 2.0, 46: 2.05, 47: 2.1, 48: 2.2, 49: 2.2, 50: 1.93, 51: 2.17, 52: 2.06, 53: 1.98, 54: 2.16, 55: 3.43, 56: 2.68, 57: 2.5, 58: 2.48, 59: 2.47, 60: 2.45, 61: 2.43, 62: 2.42, 63: 2.4, 64: 2.38, 65: 2.37, 66: 2.35, 67: 2.33, 68: 2.32, 69: 2.3, 70: 2.28, 71: 2.27, 72: 2.25, 73: 2.2, 74: 2.1, 75: 2.05, 76: 2.0, 77: 2.0, 78: 2.05, 79: 2.1, 80: 2.05, 81: 1.96, 82: 2.02, 83: 2.07, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 2.4, 91: 2.0, 92: 2.3, 93: 2.0, 94: 2.0, 95: 2.0, 96: 2.0, 97: 2.0, 98: 2.0, 99: 2.0, 100: 2.0, 101: 2.0, 102: 2.0, 103: 2.0, 104: 2.0, 105: 2.0, 106: 2.0, 107: 2.0, 108: 2.0, 109: 2.0
+}
+
+// https://doi.org/10.1515/pac-2015-0305 (table 2, 3, and 4)
+export const ElementAtomWeights: { [e: number]: number | undefined } = {
+  1: 1.008, 2: 4.0026, 3: 6.94, 4: 9.0122, 5: 10.81, 6: 10.81, 7: 14.007, 8: 15.999, 9: 18.998, 10: 20.180, 11: 22.990, 12: 24.305, 13: 26.982, 14: 28.085, 15: 30.974, 16: 32.06, 17: 35.45, 18: 39.948, 19: 39.098, 20: 40.078, 21: 44.956, 22: 47.867, 23: 50.942, 24: 51.996, 25: 54.938, 26: 55.845, 27: 58.933, 28: 58.693, 29: 63.546, 30: 65.38, 31: 69.723, 32: 72.630, 33: 74.922, 34: 78.971, 35: 79.904, 36: 83.798, 37: 85.468, 38: 87.62, 39: 88.906, 40: 91.224, 41: 92.906, 42: 95.95, 43: 96.906, 44: 101.07, 45: 102.91, 46: 106.42, 47: 107.87, 48: 112.41, 49: 114.82, 50: 118.71, 51: 121.76, 52: 127.60, 53: 127.60, 54: 131.29, 55: 132.91, 56: 137.33, 57: 138.91, 58: 140.12, 59: 140.91, 60: 144.24, 61: 144.912, 62: 150.36, 63: 151.96, 64: 157.25, 65: 158.93, 66: 162.50, 67: 164.93, 68: 167.26, 69: 168.93, 70: 173.05, 71: 174.97, 72: 178.49, 73: 180.95, 74: 183.84, 75: 186.21, 76: 190.23, 77: 192.22, 78: 195.08, 79: 196.97, 80: 200.59, 81: 204.38, 82: 207.2, 83: 208.98, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 232.04, 91: 231.04, 92: 238.03, 93: 237.048, 94: 244.064, 95: 243.061, 96: 247.070, 97: 247.070, 98: 251.079, 99: 252.083, 100: 257.095, 101: 258.098, 102: 259.101, 103: 262.110, 104: 267.122, 105: 270.131, 106: 271.134, 107: 270.133, 108: 270.134, 109: 278.156
+}
+
+export const DefaultVdwRadius = 1.7;  // C
+export const DefaultAtomWeight = 10.81;  // C
+export const DefaultAtomNumber = 0;
+
+export function VdwRadius(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultVdwRadius : ElementVdwRadii[i]!
+}
+
+export function AtomWeight(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultAtomWeight : ElementAtomWeights[i]!
+}
+
+export function AtomNumber(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultAtomNumber : i
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/coarse-grained.ts b/src/mol-model/structure/model/properties/coarse-grained.ts
deleted file mode 100644
index 392f06a2ce24f82c7c59492d9597fa2a33f015ef..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/model/properties/coarse-grained.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-import { Tensor } from 'mol-math/linear-algebra';
-import { Column } from 'mol-data/db';
-
-interface CoarseGrained {
-    isDefined: boolean,
-    modelList: mmCIF['ihm_model_list'],
-    spheres: CoarseGrained.Spheres,
-    gaussians: CoarseGrained.Gaussians
-}
-
-namespace CoarseGrained {
-    export const Empty: CoarseGrained = { isDefined: false } as any;
-
-    export const enum ElementType { Sphere, Gaussian }
-
-    export interface SiteBase {
-        asym_id: string,
-        seq_id_begin: number,
-        seq_id_end: number
-    }
-
-    export interface Sphere extends SiteBase {
-        radius: number,
-        rmsf: number
-    }
-
-    export interface Gaussian extends SiteBase {
-        weight: number,
-        covariance_matrix: Tensor.Data
-    }
-
-    type Common = {
-        count: number,
-        x: ArrayLike<number>,
-        y: ArrayLike<number>,
-        z: ArrayLike<number>,
-        modelKey: ArrayLike<number>,
-        entityKey: ArrayLike<number>
-    }
-
-    export type SiteBases =  Common & { [P in keyof SiteBase]: Column<SiteBase[P]> }
-    export type Spheres =  Common & { [P in keyof Sphere]: Column<Sphere[P]> }
-    export type Gaussians = Common & { matrix_space: Tensor.Space } & { [P in keyof Gaussian]: Column<Gaussian[P]> }
-}
-
-export default CoarseGrained;
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/coarse.ts b/src/mol-model/structure/model/properties/coarse.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d1fc3decf25a10a9a54c5924d5c4cdfaa7ab556a
--- /dev/null
+++ b/src/mol-model/structure/model/properties/coarse.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 './coarse/conformation'
+export * from './coarse/hierarchy'
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/coarse/conformation.ts b/src/mol-model/structure/model/properties/coarse/conformation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4298b80bdb8fd043149520f2b5b50d56aae59212
--- /dev/null
+++ b/src/mol-model/structure/model/properties/coarse/conformation.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import UUID from 'mol-util/uuid'
+import { Mat3 } from 'mol-math/linear-algebra';
+
+export interface CoarseConformation {
+    id: UUID,
+    spheres: CoarseSphereConformation,
+    gaussians: CoarseGaussianConformation
+}
+
+export interface CoarseSphereConformation {
+    x: ArrayLike<number>,
+    y: ArrayLike<number>,
+    z: ArrayLike<number>,
+    radius: ArrayLike<number>,
+    rmsf: ArrayLike<number>
+}
+
+export interface CoarseGaussianConformation {
+    x: ArrayLike<number>,
+    y: ArrayLike<number>,
+    z: ArrayLike<number>,
+    weight: ArrayLike<number>,
+    covariance_matrix: ArrayLike<Mat3>
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/coarse/hierarchy.ts b/src/mol-model/structure/model/properties/coarse/hierarchy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fedb463c9e7b015de3485ef2c2a90b2798df71ec
--- /dev/null
+++ b/src/mol-model/structure/model/properties/coarse/hierarchy.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
+import { Column } from 'mol-data/db'
+import { Segmentation } from 'mol-data/int';
+
+export interface CoarsedElementKeys {
+    // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
+    // are in increasing order.
+    // monotonous sequences enable for example faster secondary structure assignment.
+    isMonotonous: boolean,
+
+    // assign a key to each element
+    chainKey: ArrayLike<number>,
+    // assign a key to each element, index to the Model.entities.data table
+    entityKey: ArrayLike<number>,
+    // assign a key to each element, index to the CoarseHierarchy.models table
+    modelKey: ArrayLike<number>,
+
+    findChainKey(entityId: string, asym_id: string): number
+}
+
+export interface CoarseElementData {
+    count: number,
+    entity_id: Column<string>,
+    model_id: Column<number>,
+    asym_id: Column<string>,
+    seq_id_begin: Column<number>,
+    seq_id_end: Column<number>,
+
+    chainSegments: Segmentation
+}
+
+export type CoarseElements = CoarsedElementKeys & CoarseElementData
+
+export interface CoarseHierarchy {
+    isDefined: boolean,
+    models: mmCIF['ihm_model_list'],
+    spheres: CoarseElements,
+    gaussians: CoarseElements
+}
+
+export namespace CoarseHierarchy {
+    export const Empty: CoarseHierarchy = { isDefined: false } as any;
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/common.ts b/src/mol-model/structure/model/properties/common.ts
index aa5d93aaec136b39e51f2b22b1b5de3d6bde2c65..9dadbc95ac9d951e4d175949655012b0222a52a5 100644
--- a/src/mol-model/structure/model/properties/common.ts
+++ b/src/mol-model/structure/model/properties/common.ts
@@ -6,9 +6,7 @@
 
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 
-interface Entities {
+export interface Entities {
     data: mmCIF['entity'],
     getEntityIndex(id: string): number
-}
-
-export { Entities }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/seconday-structure.ts b/src/mol-model/structure/model/properties/seconday-structure.ts
index 04eef7fb927781a87617b89a5d6b008e483ff527..dfde631a30ecb78ca0e854b5d9868b7861f953a0 100644
--- a/src/mol-model/structure/model/properties/seconday-structure.ts
+++ b/src/mol-model/structure/model/properties/seconday-structure.ts
@@ -4,11 +4,14 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { SecondaryStructureType } from '../types';
+
 /** Secondary structure "indexed" by residues. */
-export interface SecondaryStructure {
-    type: number[],
-    index: number[],
-    flags: number[],
+interface SecondaryStructure {
+    // assign flags to each residue
+    readonly type: ArrayLike<SecondaryStructureType>,
     /** unique value for each "element". This is because single sheet is speficied by multiple records. */
-    key: number[]
-}
\ No newline at end of file
+    readonly key: ArrayLike<number>
+}
+
+export { SecondaryStructure }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts
index d7aa43951ea85c75921b0e4c9879870b73a56faa..b34b1ef61b624307682ef17e577b670444bbec36 100644
--- a/src/mol-model/structure/model/properties/sequence.ts
+++ b/src/mol-model/structure/model/properties/sequence.ts
@@ -5,7 +5,7 @@
  */
 
 import { Column } from 'mol-data/db'
-import { Hierarchy } from './hierarchy';
+import { AtomicHierarchy } from './atomic/hierarchy';
 
 interface Sequence {
     readonly byEntityKey: { [key: number]: Sequence.Entity }
@@ -19,7 +19,7 @@ namespace Sequence {
         readonly compId: Column<string>
     }
 
-    export function fromHierarchy(hierarchy: Hierarchy): Sequence {
+    export function fromAtomicHierarchy(hierarchy: AtomicHierarchy): Sequence {
         // const { label_comp_id } = hierarchy.residues;
 
         throw 'not implemented';
diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts
index d459241b4a7ff1174933c8e410e4fa5e841eddad..3b2659e9e49541c16c1e47f8ae229905a0d1ca92 100644
--- a/src/mol-model/structure/model/properties/symmetry.ts
+++ b/src/mol-model/structure/model/properties/symmetry.ts
@@ -8,6 +8,7 @@ import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { arrayFind } from 'mol-data/util'
 import { Query } from '../../query'
 import { Model } from '../../model'
+import { Spacegroup } from 'mol-math/geometry';
 
 /** Determine an atom set and a list of operators that should be applied to that set  */
 export interface OperatorGroup {
@@ -40,12 +41,14 @@ export namespace Assembly {
     }
 }
 
-interface Symmetry {
+interface ModelSymmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
+    readonly spacegroup: Spacegroup,
+    readonly isNonStandardCrytalFrame: boolean
 }
 
-namespace Symmetry {
-    export const Empty: Symmetry = { assemblies: [] };
+namespace ModelSymmetry {
+    export const Default: ModelSymmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false };
 
     export function findAssembly(model: Model, id: string): Assembly | undefined {
         const _id = id.toLocaleLowerCase();
@@ -53,4 +56,4 @@ namespace Symmetry {
     }
 }
 
-export default Symmetry
\ No newline at end of file
+export { ModelSymmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/utils/hierarchy-keys.ts b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
similarity index 88%
rename from src/mol-model/structure/model/utils/hierarchy-keys.ts
rename to src/mol-model/structure/model/properties/utils/atomic-keys.ts
index d70a26a94abdaeba5be60d137bccb7360d2b8820..1cfec2f0348e902f39787c3f2c0510687caef256 100644
--- a/src/mol-model/structure/model/utils/hierarchy-keys.ts
+++ b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
@@ -4,10 +4,9 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Column } from 'mol-data/db'
-import { Data, Segments, Keys } from '../properties/hierarchy'
+import { AtomicData, AtomicSegments, AtomicKeys } from '../atomic'
 import { Interval, Segmentation } from 'mol-data/int'
-import { Entities } from '../properties/common';
+import { Entities } from '../common'
 
 function getResidueId(comp_id: string, seq_id: number, ins_code: string) {
     return `${comp_id} ${seq_id} ${ins_code}`;
@@ -29,14 +28,14 @@ function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key
 
 function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>, residue: Map<number, Map<string, number>>) {
     const getEntKey = entities.getEntityIndex;
-    const findChainKey: Keys['findChainKey'] = (e, c) => {
+    const findChainKey: AtomicKeys['findChainKey'] = (e, c) => {
         let eKey = getEntKey(e);
         if (eKey < 0) return -1;
         const cm = chain.get(eKey)!;
         if (!cm.has(c)) return -1;
         return cm.get(c)!;
     }
-    const findResidueKey: Keys['findResidueKey'] = (e, c, name, seq, ins) => {
+    const findResidueKey: AtomicKeys['findResidueKey'] = (e, c, name, seq, ins) => {
         let eKey = getEntKey(e);
         if (eKey < 0) return -1;
         const cm = chain.get(eKey)!;
@@ -62,7 +61,7 @@ function missingEntity(k: string) {
     throw new Error(`Missing entity entry for entity id '${k}'.`);
 }
 
-function create(data: Data, entities: Entities, segments: Segments): Keys {
+export function getAtomicKeys(data: AtomicData, entities: Entities, segments: AtomicSegments): AtomicKeys {
     const { chains, residues } = data;
 
     const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
@@ -110,12 +109,10 @@ function create(data: Data, entities: Entities, segments: Segments): Keys {
 
     return {
         isMonotonous: isMonotonous && checkMonotonous(entityKey) && checkMonotonous(chainKey) && checkMonotonous(residueKey),
-        residueKey: Column.ofIntArray(residueKey),
-        chainKey: Column.ofIntArray(chainKey),
-        entityKey: Column.ofIntArray(entityKey),
+        residueKey: residueKey,
+        chainKey: chainKey,
+        entityKey: entityKey,
         findChainKey,
         findResidueKey
     };
-}
-
-export default create;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/utils/coarse-keys.ts b/src/mol-model/structure/model/properties/utils/coarse-keys.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6ef72039a5f3fa35705364b38d24d2e4bef00553
--- /dev/null
+++ b/src/mol-model/structure/model/properties/utils/coarse-keys.ts
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Entities } from '../common';
+import { CoarseElementData, CoarsedElementKeys } from '../coarse';
+
+function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) {
+    if (map.has(key)) return map.get(key)!;
+    const ret = counter.index++;
+    map.set(key, ret);
+    return ret;
+}
+
+function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key: number) {
+    if (map.has(key)) return map.get(key)!;
+    const ret = new Map<string, number>();
+    map.set(key, ret);
+    return ret;
+}
+
+function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>) {
+    const getEntKey = entities.getEntityIndex;
+    const findChainKey: CoarsedElementKeys['findChainKey'] = (e, c) => {
+        let eKey = getEntKey(e);
+        if (eKey < 0) return -1;
+        const cm = chain.get(eKey)!;
+        if (!cm.has(c)) return -1;
+        return cm.get(c)!;
+    }
+    return { findChainKey };
+}
+
+function checkMonotonous(xs: ArrayLike<number>) {
+    for (let i = 1, _i = xs.length; i < _i; i++) {
+        if (xs[i] < xs[i - 1]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+function missingEntity(k: string) {
+    throw new Error(`Missing entity entry for entity id '${k}'.`);
+}
+
+function missingModel(k: string) {
+    throw new Error(`Missing entity entry for model id '${k}'.`);
+}
+
+export function getCoarseKeys(data: CoarseElementData, modelIndex: (id: number) => number, entities: Entities): CoarsedElementKeys {
+    const { model_id, entity_id, asym_id, count, chainSegments } = data;
+
+    const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
+    const chainKey = new Int32Array(count);
+    const entityKey = new Int32Array(count);
+    const modelKey = new Int32Array(count);
+
+    for (let i = 0; i < count; i++) {
+        entityKey[i] = entities.getEntityIndex(entity_id.value(i));
+        if (entityKey[i] < 0) missingEntity(entity_id.value(i));
+        modelKey[i] = modelIndex(model_id.value(i));
+        if (modelKey[i] < 0) missingModel('' + model_id.value(i));
+    }
+
+    for (let cI = 0; cI < chainSegments.count; cI++) {
+        const start = chainSegments.segments[cI], end = chainSegments.segments[cI + 1];
+        const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]);
+        const key = getElementKey(map, asym_id.value(start), chainCounter);
+        for (let i = start; i < end; i++) chainKey[i] = key;
+    }
+
+    const { findChainKey } = createLookUp(entities, chainMaps);
+
+    return {
+        isMonotonous: checkMonotonous(entityKey) && checkMonotonous(chainKey),
+        chainKey: chainKey,
+        entityKey: entityKey,
+        modelKey: modelKey,
+        findChainKey
+    };
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/utils/guess-element.ts b/src/mol-model/structure/model/properties/utils/guess-element.ts
similarity index 100%
rename from src/mol-model/structure/model/utils/guess-element.ts
rename to src/mol-model/structure/model/properties/utils/guess-element.ts
diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts
index caa1ee2c85af1c68e2bdca9a9e0fdd90de87b23b..36d0cb935f3631e5fd0057526443f753472090f6 100644
--- a/src/mol-model/structure/model/types.ts
+++ b/src/mol-model/structure/model/types.ts
@@ -94,6 +94,7 @@ export namespace SecondaryStructureType {
     export const Turn = ['s', 't', 'l', '']
 
     export const is: (ss: SecondaryStructureType, f: Flag) => boolean = BitFlags.has
+    export const create: (fs: Flag) => SecondaryStructureType = BitFlags.create
 
     export const enum Flag {
         None = 0x0,
diff --git a/src/mol-model/structure/query.ts b/src/mol-model/structure/query.ts
index 55dff3a8e548d080534bb4f171d356689fbfc9b1..b5886cbda873b29ebfbf23534c64609b8f1197b2 100644
--- a/src/mol-model/structure/query.ts
+++ b/src/mol-model/structure/query.ts
@@ -7,11 +7,13 @@
 import Selection from './query/selection'
 import Query from './query/query'
 import * as generators from './query/generators'
+import * as modifiers from './query/modifiers'
 import props from './query/properties'
 import pred from './query/predicates'
 
 export const Queries = {
     generators,
+    modifiers,
     props,
     pred
 }
diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts
index 66c9f0415dd28f02c0f2ad74eac035ec09d305eb..0650a17ae0690e5147a54239991436c8eda6fde9 100644
--- a/src/mol-model/structure/query/generators.ts
+++ b/src/mol-model/structure/query/generators.ts
@@ -7,10 +7,11 @@
 import Query from './query'
 import Selection from './selection'
 import P from './properties'
-import { Structure, ElementSet, Element, Unit } from '../structure'
+import { Element, Unit } from '../structure'
 import { OrderedSet, Segmentation } from 'mol-data/int'
+import { LinearGroupingBuilder } from './utils/builders';
 
-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,165 +46,116 @@ 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;
+        let progress = 0;
+        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 });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, 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);
 
+        let progress = 0;
+        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.atomicHierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.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 });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, 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>();
-
-    add(key: any, unit: number, atom: number) {
-        let b = this.builderMap.get(key);
-        if (!b) {
-            b = ElementSet.LinearBuilder(this.structure.elements);
-            this.builders[this.builders.length] = b;
-            this.builderMap.set(key, b);
-        }
-        b.add(unit, atom);
-    }
-
-    private allSingletons() {
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            if (this.builders[i].elementCount > 1) return false;
-        }
-        return true;
-    }
-
-    private singletonSelection(): Selection {
-        const atoms: Element[] = Element.createEmptyArray(this.builders.length);
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            atoms[i] = this.builders[i].singleton();
-        }
-        return Selection.Singletons(this.structure, ElementSet.ofAtoms(atoms, this.structure.elements));
-    }
-
-    private fullSelection() {
-        const sets: ElementSet[] = new Array(this.builders.length);
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            sets[i] = this.builders[i].getSet();
-        }
-        return Selection.Sequence(this.structure, sets);
-    }
-
-    getSelection(): Selection {
-        const len = this.builders.length;
-        if (len === 0) return Selection.Empty(this.structure);
-        if (this.allSingletons()) return this.singletonSelection();
-        return this.fullSelection();
-    }
-
-    constructor(private structure: 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];
-
+        let progress = 0;
+        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.atomicHierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.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 });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, max: units.length });
         }
 
         return builder.getSelection();
diff --git a/src/mol-model/structure/query/modifiers.ts b/src/mol-model/structure/query/modifiers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b332ff7fcfc9d142ae080968375a3c54af0eedb
--- /dev/null
+++ b/src/mol-model/structure/query/modifiers.ts
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Segmentation } from 'mol-data/int';
+import { RuntimeContext } from 'mol-task';
+import { Structure, Unit } from '../structure';
+import Query from './query';
+import Selection from './selection';
+import { UniqueStructuresBuilder } from './utils/builders';
+import { StructureUniqueSubsetBuilder } from '../structure/util/unique-subset-builder';
+
+function getWholeResidues(ctx: RuntimeContext, source: Structure, structure: Structure) {
+    const builder = source.subsetBuilder(true);
+    for (const unit of structure.units) {
+        if (unit.kind !== Unit.Kind.Atomic) {
+            // just copy non-atomic units.
+            builder.setUnit(unit.id, unit.elements);
+            continue;
+        }
+
+        const { residueSegments } = unit.model.atomicHierarchy;
+
+        const elements = unit.elements;
+        builder.beginUnit(unit.id);
+        const residuesIt = Segmentation.transientSegments(residueSegments, elements);
+        while (residuesIt.hasNext) {
+            const rI = residuesIt.move().index;
+            for (let j = residueSegments.segments[rI], _j = residueSegments.segments[rI + 1]; j < _j; j++) {
+                builder.addElement(j);
+            }
+        }
+        builder.commitUnit();
+    }
+    return builder.getStructure();
+}
+
+export function wholeResidues(query: Query.Provider, isFlat: boolean): Query.Provider {
+    return async (structure, ctx) => {
+        const inner = await query(structure, ctx);
+        if (Selection.isSingleton(inner)) {
+            return Selection.Singletons(structure, getWholeResidues(ctx, structure, inner.structure));
+        } else {
+            const builder = new UniqueStructuresBuilder(structure);
+            let progress = 0;
+            for (const s of inner.structures) {
+                builder.add(getWholeResidues(ctx, structure, s));
+                progress++;
+                if (ctx.shouldUpdate) await ctx.update({ message: 'Whole Residues', current: progress, max: inner.structures.length });
+            }
+            return builder.getSelection();
+        }
+    };
+}
+
+
+// export function groupBy()  ...
+
+export interface IncludeSurroundingsParams {
+    radius: number,
+    // atomRadius?: Element.Property<number>,
+    wholeResidues?: boolean
+}
+
+async function getIncludeSurroundings(ctx: RuntimeContext, source: Structure, structure: Structure, params: IncludeSurroundingsParams) {
+    const builder = new StructureUniqueSubsetBuilder(source);
+    const lookup = source.lookup3d;
+    const r = params.radius;
+
+    let progress = 0;
+    for (const unit of structure.units) {
+        const { x, y, z } = unit.conformation;
+        const elements = unit.elements;
+        for (let i = 0, _i = elements.length; i < _i; i++) {
+            const e = elements[i];
+            lookup.findIntoBuilder(x(e), y(e), z(e), r, builder);
+        }
+        progress++;
+        if (progress % 2500 === 0 && ctx.shouldUpdate) await ctx.update({ message: 'Include Surroudnings', isIndeterminate: true });
+    }
+    return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure();
+}
+
+export function includeSurroundings(query: Query.Provider, params: IncludeSurroundingsParams): Query.Provider {
+    return async (structure, ctx) => {
+        const inner = await query(structure, ctx);
+        if (Selection.isSingleton(inner)) {
+            const surr = await getIncludeSurroundings(ctx, structure, inner.structure, params);
+            const ret = Selection.Singletons(structure, surr);
+            return ret;
+        } else {
+            const builder = new UniqueStructuresBuilder(structure);
+            for (const s of inner.structures) {
+                builder.add(await getIncludeSurroundings(ctx, structure, s, params));
+            }
+            return builder.getSelection();
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts
index 98d22ce51ca05136bc70f0bf52c82399d8fbd400..3299acaaee21a426e2e3069845768e132b63aca3 100644
--- a/src/mol-model/structure/query/properties.ts
+++ b/src/mol-model/structure/query/properties.ts
@@ -5,7 +5,6 @@
  */
 
 import { Element, Unit } from '../structure'
-import CoarseGrained from '../model/properties/coarse-grained';
 import { VdwRadius } from '../model/properties/atomic';
 
 const constant = {
@@ -27,67 +26,68 @@ 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.atomicConformation.atomId.value(l.element)),
+    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
+    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.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.atomicHierarchy.atoms.type_symbol.value(l.element)),
+    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
+    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
+    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
+    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.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.atomicHierarchy.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.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
+
+    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
+    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
+    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.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.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
+
+    // Properties
+    secondary_structure_type: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
+    secondary_structure_key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[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.atomicHierarchy.chainKey[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.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
+    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
+    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
 }
 
-const coarse_grained = {
-    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.modelKey[l.element]),
-    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.entityKey[l.element]),
+const coarse = {
+    key: atom.key,
+    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.modelKey[l.element]),
+    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
 
     x: atom.x,
     y: atom.y,
     z: atom.z,
 
-    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.asym_id.value(l.element)),
-    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.seq_id_begin.value(l.element)),
-    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.seq_id_end.value(l.element)),
+    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
+    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
+    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
 
-    sphere_radius: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Sphere
-        ? notCoarse('spheres') : l.unit.spheres.radius.value(l.element)),
-    sphere_rmsf: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Sphere
-        ? notCoarse('spheres') : l.unit.spheres.rmsf.value(l.element)),
+    sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
+    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
 
-    gaussian_weight: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Gaussian
-        ? notCoarse('gaussians') : l.unit.gaussians.weight.value(l.element)),
-    gaussian_covariance_matrix: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Gaussian
-        ? notCoarse('gaussians') : l.unit.gaussians.covariance_matrix.value(l.element)),
+    gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
+    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[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.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; }
 
 const entity = {
     key: eK,
@@ -105,7 +105,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)
 }
 
@@ -116,7 +116,7 @@ const Properties = {
     chain,
     entity,
     unit,
-    coarse_grained
+    coarse
 }
 
 type Properties = typeof Properties
diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts
index f9f516710817ea7ae9d8c042457a7f81e2f53a2d..a1fc34f2a0e6f741a667aa97c2af2fcd7ec7a9c2 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 { structureUnion } from './utils/structure';
 
 // 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 structureUnion(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, structureUnion(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) { }
     }
diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e1d6d7cfaea643c2a41942f3ff2bebc14b65cb3
--- /dev/null
+++ b/src/mol-model/structure/query/utils/builders.ts
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Element, Structure } from '../../structure';
+import Selection from '../selection';
+import { HashSet } from 'mol-data/generic';
+import { structureUnion } from './structure';
+import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
+
+export class UniqueStructuresBuilder {
+    private set = HashSet(Structure.hashCode, Structure.areEqual);
+    private structures: Structure[] = [];
+    private allSingletons = true;
+
+    add(s: Structure) {
+        if (!s.elementCount) return;
+        if (s.elementCount !== 1) this.allSingletons = false;
+        if (this.set.add(s)) {
+            this.structures[this.structures.length] = s;
+        }
+    }
+
+    getSelection() {
+        if (this.allSingletons) return Selection.Singletons(this.source, structureUnion(this.source, this.structures));
+        return Selection.Sequence(this.source, this.structures);
+    }
+
+    constructor(private source: Structure) {
+    }
+}
+
+export class LinearGroupingBuilder {
+    private builders: StructureSubsetBuilder[] = [];
+    private builderMap = new Map<string, StructureSubsetBuilder>();
+
+    add(key: any, unit: number, element: number) {
+        let b = this.builderMap.get(key);
+        if (!b) {
+            b = this.source.subsetBuilder(true);
+            this.builders[this.builders.length] = b;
+            this.builderMap.set(key, b);
+        }
+        b.addToUnit(unit, element);
+    }
+
+    private allSingletons() {
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            if (this.builders[i].elementCount > 1) return false;
+        }
+        return true;
+    }
+
+    private singletonSelection(): Selection {
+        const builder = this.source.subsetBuilder(true);
+        const loc = Element.Location();
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            this.builders[i].setSingletonLocation(loc);
+            builder.addToUnit(loc.unit.id, loc.element);
+        }
+        return Selection.Singletons(this.source, builder.getStructure());
+    }
+
+    private fullSelection() {
+        const structures: Structure[] = new Array(this.builders.length);
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            structures[i] = this.builders[i].getStructure();
+        }
+        return Selection.Sequence(this.source, structures);
+    }
+
+    getSelection(): Selection {
+        const len = this.builders.length;
+        if (len === 0) return Selection.Empty(this.source);
+        if (this.allSingletons()) return this.singletonSelection();
+        return this.fullSelection();
+    }
+
+    constructor(private source: Structure) { }
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/query/utils/structure.ts b/src/mol-model/structure/query/utils/structure.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f30fd20e67475592bf886154f0ceb6434e3854b5
--- /dev/null
+++ b/src/mol-model/structure/query/utils/structure.ts
@@ -0,0 +1,104 @@
+/**
+ * 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';
+import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
+
+export function structureUnion(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: StructureSubsetBuilder, elements: SortedArray, id: number) {
+    this.setUnit(id, elements);
+}
+
+export function structureAreIntersecting(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 structureIntersect(sA: Structure, sB: Structure): Structure {
+    if (sA === sB) return sA;
+    if (!structureAreIntersecting(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 structureSubtract(a: Structure, b: Structure): Structure {
+    if (a === b) return Structure.Empty;
+    if (!structureAreIntersecting(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);
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts
index 59f200b700ccd406f08a897a847e84b60acc7bee..c57bb2a47cb40b67ac59da9cd567439d728d49c7 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'
+import StructureSymmetry from './structure/symmetry'
 
-export { Element, ElementSet, ElementGroup, Structure, Unit, Symmetry }
\ No newline at end of file
+export { Element, Structure, Unit, StructureSymmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts
index f730ed988d5fe55d1ace4a16eac6cc82331243c0..e62dcf1333a3585005a1075391a2314152e4f21d 100644
--- a/src/mol-model/structure/structure/element.ts
+++ b/src/mol-model/structure/structure/element.ts
@@ -15,8 +15,8 @@ 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 index: (e: Element) => number = Tuple.snd;
+    export const unitId: (e: Element) => number = Tuple.fst;
+    export const elementIndex: (e: Element) => number = Tuple.snd;
     export const areEqual: (e: Element, b: Element) => boolean = Tuple.areEqual;
     export const hashCode: (e: Element) => number = Tuple.hashCode;
 
@@ -29,8 +29,8 @@ namespace Element {
     export interface Predicate extends Property<boolean> { }
 
     export function updateLocation(structure: Structure, l: Location, element: Element) {
-        l.unit = structure.units[unit(element)];
-        l.element = index(element);
+        l.unit = structure.units[unitId(element)];
+        l.element = elementIndex(element);
         return l;
     }
 
diff --git a/src/mol-model/structure/structure/element/group.ts b/src/mol-model/structure/structure/element/group.ts
deleted file mode 100644
index e89b3d37e87b6611a0630fe4978fe1218ceaa76b..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/element/group.ts
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/impl/properties.ts b/src/mol-model/structure/structure/element/impl/properties.ts
deleted file mode 100644
index 03ae27d163b402e0026bcfefd54bd706739187a0..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/element/impl/properties.ts
+++ /dev/null
@@ -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?
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/impl/set-builder.ts b/src/mol-model/structure/structure/element/impl/set-builder.ts
deleted file mode 100644
index 28956aa832d6d267856f512e00984a1452b0bdcc..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/element/impl/set-builder.ts
+++ /dev/null
@@ -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);
-}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/impl/set.ts b/src/mol-model/structure/structure/element/impl/set.ts
deleted file mode 100644
index 2aaf3182e5f1ea62f0c313357d3b48d6b8a5dd4c..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/element/impl/set.ts
+++ /dev/null
@@ -1,464 +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'
-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
-}
-
-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();
-}
-
-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[] = [];
-    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));
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/set.ts b/src/mol-model/structure/structure/element/set.ts
deleted file mode 100644
index dddebe8bfd28b73505e0c93d18840a68087d83d4..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/element/set.ts
+++ /dev/null
@@ -1,67 +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 { ElementSetLookup3D } 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
-
-    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?
-}
-
-interface ElementSet { '@type': 'element-set' }
-
-export default ElementSet
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 6572e03e1ea8988ac436a44c2259fc1223a988d9..79b21a33945594527cc5b649acd99bd15787c30f 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -4,109 +4,221 @@
  * @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 { Model } from '../model'
+import { sort, arraySwap, hash1 } from 'mol-data/util';
 import Element from './element'
-import CoarseGrained from '../model/properties/coarse-grained';
+import Unit from './unit'
+import { StructureLookup3D } from './util/lookup3d';
+import { CoarseElements } from '../model/properties/coarse';
+import { StructureSubsetBuilder } from './util/subset-builder';
+
+class Structure {
+    readonly unitMap: IntMap<Unit>;
+    readonly units: ReadonlyArray<Unit>;
+    readonly elementCount: number;
+
+    private _hashCode = 0;
+
+    subsetBuilder(isSorted: boolean) {
+        return new StructureSubsetBuilder(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);
+    }
 
-// 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
+    get boundary() {
+        return this.lookup3d.boundary;
+    }
+
+    private _lookup3d?: StructureLookup3D = void 0;
+    get lookup3d() {
+        if (this._lookup3d) return this._lookup3d;
+        this._lookup3d = new StructureLookup3D(this);
+        return this._lookup3d;
+    }
+
+    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 ofData(format: Format) {
-        const models = Model.create(format);
-        return models.map(ofModel);
-    }
+    export function create(units: ReadonlyArray<Unit>): Structure { return new Structure(units); }
 
     export function ofModel(model: Model): Structure {
-        const chains = model.hierarchy.chainSegments;
-        const builder = Builder();
+        const chains = model.atomicHierarchy.chainSegments;
+        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.createAtomic(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;
+        const cs = model.coarseHierarchy;
         if (cs.isDefined) {
             if (cs.spheres.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.spheres.count));
-                const unit = Unit.createCoarse(model, SymmetryOperator.Default, group, CoarseGrained.ElementType.Sphere);
-                builder.add(unit, unit.fullGroup);
+                addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
             }
             if (cs.gaussians.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.gaussians.count));
-                const unit = Unit.createCoarse(model, SymmetryOperator.Default, group, CoarseGrained.ElementType.Gaussian);
-                builder.add(unit, unit.fullGroup);
+                addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
             }
         }
 
         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
+    function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
+        const { chainSegments } = elements;
+        for (let cI = 0; cI < chainSegments.count; cI++) {
+            const elements = SortedArray.ofBounds(chainSegments.segments[cI], chainSegments.segments[cI + 1]);
+            builder.addUnit(kind, model, SymmetryOperator.Default, elements);
+        }
     }
 
-    class BuilderImpl implements Builder {
-        private _unitId = 0;
+    export class StructureBuilder {
         private units: Unit[] = [];
-        private elements = ElementSet.Generator();
-        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); }
-    }
+        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;
+        }
 
-    export function Builder(): Builder { return new BuilderImpl(); }
+        addWithOperator(unit: Unit, operator: SymmetryOperator): Unit {
+            const newUnit = unit.applyOperator(this.units.length, operator);
+            this.units.push(newUnit);
+            return newUnit;
+        }
 
-    /** 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 {
+            return create(this.units);
+        }
+
+        get isEmpty() {
+            return this.units.length === 0;
+        }
     }
 
+    export function Builder() { return new StructureBuilder(); }
+
     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 maxIdx = 0;
+        private idx = -1;
+
+        hasNext: boolean;
+        move(): Element.Location {
+            this.advance();
+            this.current.element = this.elements[this.idx];
+            return this.current;
+        }
+
+        private advance() {
+            if (this.idx < this.maxIdx) {
+                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.maxIdx = this.elements.length - 1;
+        }
+
+        constructor(private structure: Structure) {
+            this.hasNext = structure.elementCount > 0;
+            if (this.hasNext) {
+                this.elements = structure.units[0].elements;
+                this.maxIdx = this.elements.length - 1;
+                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..0a912a4ab1ab61f8d08cb533ca052e634a81e538 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -5,43 +5,102 @@
  */
 
 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';
+import { SortedArray } from 'mol-data/int';
+import Unit from './unit';
+import { EquivalenceClasses, hash2 } from 'mol-data/util';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { SymmetryOperator, Spacegroup, SpacegroupCell } from 'mol-math/geometry';
 
-namespace Symmetry {
-    export const buildAssembly = buildAssemblyImpl;
-}
+namespace StructureSymmetry {
+    export function buildAssembly(structure: Structure, asmName: string) {
+        return Task.create('Build Assembly', async ctx => {
+            const models = Structure.getModels(structure);
+            if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
+
+            const assembly = ModelSymmetry.findAssembly(models[0], asmName);
+            if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
-export default Symmetry;
+            const assembler = Structure.Builder();
 
-function buildAssemblyImpl(structure: Structure, name: string) {
-    return Task.create('Build Symmetry', async ctx => {
-        const models = Structure.getModels(structure);
-        if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
+            for (const g of assembly.operatorGroups) {
+                const selection = await g.selector(structure).runAsChild(ctx);
+                if (Selection.structureCount(selection) === 0) {
+                    continue;
+                }
+                const { units } = Selection.unionStructure(selection);
 
-        const assembly = ModelSymmetry.findAssembly(models[0], name);
-        if (!assembly) throw new Error(`Assembly '${name}' is not defined.`);
+                for (const oper of g.operators) {
+                    for (const unit of units) {
+                        assembler.addWithOperator(unit, oper);
+                    }
+                }
+            }
 
-        const assembler = Structure.Builder();
+            return assembler.getStructure();
+        });
+    }
 
-        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);
+    // TODO: build symmetry mates within radius
+
+    export function buildSymmetryRange(structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
+        return Task.create('Build Assembly', async ctx => {
+            const models = Structure.getModels(structure);
+            if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
+
+            const { spacegroup } = models[0].symmetry;
+            if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
+
+            const operators: SymmetryOperator[] = [];
+            for (let op = 0; op < spacegroup.operators.length; op++) {
+                for (let i = ijkMin[0]; i < ijkMax[0]; i++) {
+                    for (let j = ijkMin[1]; j < ijkMax[1]; j++) {
+                        for (let k = ijkMin[2]; k < ijkMax[2]; k++) {
+                            operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k);
+                        }
+                    }
+                }
+            }
 
-            const unitIds = ElementSet.unitIndices(elements);
+            const assembler = Structure.Builder();
 
-            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));
+            const { units } = structure;
+            for (const oper of operators) {
+                for (const unit of units) {
+                    assembler.addWithOperator(unit, oper);
                 }
             }
+
+            return assembler.getStructure();
+        });
+    }
+
+    function hashUnit(u: Unit) {
+        return hash2(u.invariantId, SortedArray.hashCode(u.elements));
+    }
+
+    function areUnitsEquivalent(a: Unit, b: Unit) {
+        return a.invariantId === b.invariantId && a.model.id === b.model.id && SortedArray.areEqual(a.elements, b.elements);
+    }
+
+    export function UnitEquivalenceBuilder() {
+        return EquivalenceClasses<number, Unit>(hashUnit, areUnitsEquivalent);
+    }
+
+    export function getTransformGroups(s: Structure): ReadonlyArray<Unit.SymmetryGroup> {
+        const groups = UnitEquivalenceBuilder();
+        for (const u of s.units) groups.add(u.id, u);
+
+        const ret: Unit.SymmetryGroup[] = [];
+        for (const eqUnits of groups.groups) {
+            const first = s.unitMap.get(eqUnits[0]);
+            ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) });
         }
 
-        return assembler.getStructure();
-    });
-}
\ No newline at end of file
+        return ret;
+    }
+}
+
+export default StructureSymmetry;
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index 596dd3580cddfa4d32aaee7cfaae507e1d6925dd..5c34fd555248e6b3c2cf752446930b7d47116f6e 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -5,34 +5,53 @@
  */
 
 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 CoarseGrained from '../model/properties/coarse-grained';
+import { GridLookup3D, Lookup3D } from 'mol-math/geometry'
+import { SortedArray } from 'mol-data/int';
+import { idFactory } from 'mol-util/id-factory';
+import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds'
+import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
+import { ValueRef } from 'mol-util';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
-type Unit = Unit.Atomic | Unit.Coarse
+type Unit = Unit.Atomic | Unit.Spheres | Unit.Gaussians
 
 namespace Unit {
-    export const enum Kind { Atomic, Coarse }
+    export const enum Kind { Atomic, Spheres, Gaussians }
 
     export function isAtomic(u: Unit): u is Atomic { return u.kind === Kind.Atomic; }
-    export function isCoarse(u: Unit): u is Coarse { return u.kind === Kind.Coarse; }
+    export function isCoarse(u: Unit): u is Spheres | Gaussians { return u.kind === Kind.Spheres || u.kind === Kind.Gaussians; }
+    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 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.atomicConformation), AtomicProperties());
+            case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres));
+            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians));
+        }
+    }
 
-    export interface Base extends SymmetryOperator.ArrayMapping {
-        // Provides access to the underlying data.
+    // A group of units that differ only by symmetry operators.
+    export type SymmetryGroup = { readonly elements: SortedArray, readonly units: ReadonlyArray<Unit> }
+
+    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,
+
+        getChild(elements: SortedArray): Unit,
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
 
-        // 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
+        readonly lookup3d: Lookup3D
     }
 
+    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)
@@ -40,91 +59,121 @@ 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: Unit.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>;
 
-    // Coarse grained representations.
-    export interface Coarse extends Base  {
-        readonly kind: Unit.Kind.Coarse,
-        readonly elementType: CoarseGrained.ElementType,
+        private props: AtomicProperties;
 
-        readonly siteBases: CoarseGrained.SiteBases,
-        readonly spheres: CoarseGrained.Spheres,
-        readonly gaussians: CoarseGrained.Gaussians
+        getChild(elements: SortedArray): Unit {
+            if (elements.length === this.elements.length) return this;
+            return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation, AtomicProperties());
+        }
+
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
+            const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
+            return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props);
+        }
+
+        get lookup3d() {
+            if (this.props.lookup3d.ref) return this.props.lookup3d.ref;
+            const { x, y, z } = this.model.atomicConformation;
+            this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
+            return this.props.lookup3d.ref;
+        }
+
+        get bonds() {
+            if (this.props.bonds.ref) return this.props.bonds.ref;
+            this.props.bonds.ref = computeIntraUnitBonds(this);
+            return this.props.bonds.ref;
+        }
+
+        constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+
+            this.residueIndex = model.atomicHierarchy.residueSegments.segmentMap;
+            this.chainIndex = model.atomicHierarchy.chainSegments.segmentMap;
+            this.props = props;
+        }
     }
 
-    export 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
-        };
+    interface AtomicProperties {
+        lookup3d: ValueRef<Lookup3D | undefined>,
+        bonds: ValueRef<IntraUnitBonds | undefined>,
     }
 
-    export function createCoarse(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup, elementType: CoarseGrained.ElementType): Unit.Coarse {
-        const siteBases = elementType === CoarseGrained.ElementType.Sphere ? model.coarseGrained.spheres : model.coarseGrained.gaussians;
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, siteBases);
-
-        return {
-            model,
-            kind: Kind.Coarse,
-            elementType,
-            operator,
-            fullGroup,
-            siteBases,
-            spheres: model.coarseGrained.spheres,
-            gaussians: model.coarseGrained.gaussians,
-            invariantPosition,
-            position,
-            x, y, z
-        };
+    function AtomicProperties() {
+        return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0) };
     }
 
-    export function withOperator(unit: Unit, operator: SymmetryOperator): Unit {
-        switch (unit.kind) {
-            case Kind.Atomic: return createAtomic(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup);
-            case Kind.Coarse: return createCoarse(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup, unit.elementType);
+    class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
+        readonly kind: K;
+
+        readonly id: number;
+        readonly invariantId: number;
+        readonly elements: SortedArray;
+        readonly model: Model;
+        readonly conformation: SymmetryOperator.ArrayMapping;
+
+        readonly coarseElements: CoarseElements;
+        readonly coarseConformation: C;
+
+        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, elements, this.conformation);
         }
-    }
 
-    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__;
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
+            const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
+            const ret = createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements()));
+            (ret as Coarse<K, C>)._lookup3d = this._lookup3d;
+            return ret;
         }
 
-        throw 'not implemented';
-    }
+        private _lookup3d: ValueRef<Lookup3D | undefined> = ValueRef.create(void 0);
+        get lookup3d() {
+            if (this._lookup3d.ref) return this._lookup3d.ref;
+            // TODO: support sphere radius?
+            const { x, y, z } = this.getCoarseElements();
+            this._lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
+            return this._lookup3d.ref;
+        }
+
+        private getCoarseElements() {
+            return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
+        }
 
-    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__;
+        constructor(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+            this.kind = kind;
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+            this.coarseElements = kind === Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
+            this.coarseConformation = (kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians) as C;
         }
+    }
 
-        throw 'not implemented';
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
+        return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
     }
+
+    export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { }
+    export class Gaussians extends Coarse<Kind.Gaussians, CoarseGaussianConformation> { }
 }
 
 export default Unit;
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/bonds.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6316c3eebbdd5a53cc1c3a9b8fd85c6a6cf4dd15
--- /dev/null
+++ b/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'
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/properties/bonds/group-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
similarity index 93%
rename from src/mol-model/structure/structure/element/properties/bonds/group-compute.ts
rename to src/mol-model/structure/structure/unit/bonds/intra-compute.ts
index a75540509204cff433c98804911a2cb9bfe7961a..9cd4a9ed1839ec43517ce9e9c46ecb06eb8d31f7 100644
--- a/src/mol-model/structure/structure/element/properties/bonds/group-compute.ts
+++ b/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 { 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 { x, y, z } = unit.model.atomicConformation;
+    const atomCount = unit.elements.length;
+    const { elements: atoms, residueIndex } = unit;
+    const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
+    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    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 }
\ No newline at end of file
+export { computeIntraUnitBonds }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/element/properties/bonds/group-data.ts b/src/mol-model/structure/structure/unit/bonds/intra-data.ts
similarity index 88%
rename from src/mol-model/structure/structure/element/properties/bonds/group-data.ts
rename to src/mol-model/structure/structure/unit/bonds/intra-data.ts
index 0406f05d326db903c081be750001b069b4a89578..c897e6ca63ed8dff5071f1c52def66e41f3bf935 100644
--- a/src/mol-model/structure/structure/element/properties/bonds/group-data.ts
+++ b/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 }
\ No newline at end of file
+export { IntraUnitBonds }
\ No newline at end of file
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..beb7c150a40cc72668c78134543d78c80342d41a 100644
--- a/src/mol-model/structure/structure/util/lookup3d.ts
+++ b/src/mol-model/structure/structure/util/lookup3d.ts
@@ -7,98 +7,110 @@
 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';
-
-interface ElementSetLookup3D extends Lookup3D<Element> {}
-
-namespace ElementSetLookup3D {
-    class Impl implements ElementSetLookup3D {
-        private unitLookup: Lookup3D;
-        private result = Result.create<Element>();
-        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]);
-                }
+import { OrderedSet } from 'mol-data/int';
+import { StructureUniqueSubsetBuilder } from './unique-subset-builder';
+
+export class StructureLookup3D implements Lookup3D<Element> {
+    private unitLookup: Lookup3D;
+    private result = Result.create<Element>();
+    private pivot = Vec3.zero();
+
+    find(x: number, y: number, z: number, radius: number): Result<Element> {
+        Result.reset(this.result);
+        const { units } = 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 unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const unitLookup = unit.lookup3d;
+            const groupResult = unitLookup.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(unit.id, 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;
-            }
+        return this.result;
+    }
 
-            return false;
-        }
+    findIntoBuilder(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder) {
+        const { units } = this.structure;
+        const closeUnits = this.unitLookup.find(x, y, z, radius);
+        if (closeUnits.count === 0) return;
 
-        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;
+        for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            const unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const unitLookup = unit.lookup3d;
+            const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
+            if (groupResult.count === 0) continue;
+
+            const elements = unit.elements;
+            builder.beginUnit(unit.id);
+            for (let j = 0, _j = groupResult.count; j < _j; j++) {
+                builder.addElement(elements[groupResult.indices[j]]);
             }
+            builder.commitUnit();
+        }
+    }
+
+    check(x: number, y: number, z: number, radius: number): boolean {
+        const { units } = this.structure;
+        const closeUnits = this.unitLookup.find(x, y, z, radius);
+        if (closeUnits.count === 0) return false;
 
-            this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
-            this.boundary = computeStructureBoundary(structure);
+        for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            const unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const groupLookup = unit.lookup3d;
+            if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
         }
+
+        return false;
     }
 
-    export function create(s: Structure): ElementSetLookup3D {
-        return new Impl(s);
+    _boundary: { box: Box3D; sphere: Sphere3D; } | undefined = void 0;
+
+    get boundary() {
+        if (this.boundary) return this._boundary!;
+        this._boundary = computeStructureBoundary(this.structure);
+        return this._boundary!;
     }
-}
 
-export { ElementSetLookup3D }
\ No newline at end of file
+    constructor(private structure: Structure) {
+        const { units } = structure;
+        const unitCount = units.length;
+        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 unit = units[i];
+            const lookup = unit.lookup3d;
+            const s = lookup.boundary.sphere;
+
+            Vec3.transformMat4(center, s.center, unit.conformation.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) });
+    }
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7c143774272e1e4350a90886289aa4c14e70adf0
--- /dev/null
+++ b/src/mol-model/structure/structure/util/subset-builder.ts
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { IntMap, SortedArray } from 'mol-data/int';
+import { sortArray } from 'mol-data/util';
+import Element from '../element';
+import StructureSymmetry from '../symmetry';
+import Unit from '../unit';
+import Structure from '../structure';
+
+export class StructureSubsetBuilder {
+    private ids: number[] = [];
+    private unitMap = IntMap.Mutable<number[]>();
+    private parentId = -1;
+    private currentUnit: number[] = [];
+    elementCount = 0;
+
+    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++;
+    }
+
+    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;
+    }
+
+    private _getStructure(deduplicateElements: boolean): Structure {
+        if (this.isEmpty) return Structure.Empty;
+
+        const newUnits: Unit[] = [];
+        sortArray(this.ids);
+
+        const symmGroups = StructureSymmetry.UnitEquivalenceBuilder();
+
+        for (let i = 0, _i = this.ids.length; i < _i; i++) {
+            const id = this.ids[i];
+            const parent = this.parent.unitMap.get(id);
+
+            let unit: ArrayLike<number> = this.unitMap.get(id);
+            let sorted = false;
+
+            if (deduplicateElements) {
+                if (!this.isSorted) sortArray(unit);
+                unit = SortedArray.deduplicate(SortedArray.ofSortedArray(this.currentUnit));
+                sorted = true;
+            }
+
+            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;
+                symmGroups.add(parent.id, parent);
+                continue;
+            }
+
+            if (!this.isSorted && !sorted && l > 1) sortArray(unit);
+
+            let child = parent.getChild(SortedArray.ofSortedArray(unit));
+            const pivot = symmGroups.add(child.id, child);
+            if (child !== pivot) child = pivot.applyOperator(child.id, child.conformation.operator, true);
+            newUnits[newUnits.length] = child;
+        }
+
+        return Structure.create(newUnits);
+    }
+
+    getStructure() {
+        return this._getStructure(false);
+    }
+
+    getStructureDeduplicate() {
+        return this._getStructure(true);
+    }
+
+    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() {
+        return this.elementCount === 0;
+    }
+
+    constructor(private parent: Structure, private isSorted: boolean) {
+
+    }
+}
diff --git a/src/mol-model/structure/structure/util/unique-subset-builder.ts b/src/mol-model/structure/structure/util/unique-subset-builder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc18e6519217570f2bb6e6b8097e5baa3e2d6f96
--- /dev/null
+++ b/src/mol-model/structure/structure/util/unique-subset-builder.ts
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { IntMap, SortedArray } from 'mol-data/int';
+import { sortArray } from 'mol-data/util';
+import StructureSymmetry from '../symmetry';
+import Unit from '../unit';
+import Structure from '../structure';
+import { UniqueArray } from 'mol-data/generic';
+
+type UArray = UniqueArray<number, number>
+
+export class StructureUniqueSubsetBuilder {
+    private ids: number[] = [];
+    private unitMap = IntMap.Mutable<UArray>();
+    private parentId = -1;
+    private currentUnit: UArray = UniqueArray.create();
+    elementCount = 0;
+
+    addToUnit(parentId: number, e: number) {
+        const unit = this.unitMap.get(parentId);
+        if (!!unit) {
+            if (UniqueArray.add(unit, e, e)) this.elementCount++;
+        }
+        else {
+            const arr: UArray = UniqueArray.create();
+            UniqueArray.add(arr, e, e);
+            this.unitMap.set(parentId, arr);
+            this.ids[this.ids.length] = parentId;
+            this.elementCount++;
+        }
+    }
+
+    beginUnit(parentId: number) {
+        this.parentId = parentId;
+        if (this.unitMap.has(parentId)) {
+            this.currentUnit = this.unitMap.get(parentId);
+        } else {
+            this.currentUnit = this.currentUnit.array.length > 0 ? UniqueArray.create() : this.currentUnit;
+        }
+    }
+
+    addElement(e: number) {
+        if (UniqueArray.add(this.currentUnit, e, e)) this.elementCount++;
+    }
+
+    commitUnit() {
+        if (this.currentUnit.array.length === 0 || this.unitMap.has(this.parentId)) return;
+        this.ids[this.ids.length] = this.parentId;
+        this.unitMap.set(this.parentId, this.currentUnit);
+        this.parentId = -1;
+    }
+
+    getStructure(): Structure {
+        if (this.isEmpty) return Structure.Empty;
+
+        const newUnits: Unit[] = [];
+        sortArray(this.ids);
+
+        const symmGroups = StructureSymmetry.UnitEquivalenceBuilder();
+
+        for (let i = 0, _i = this.ids.length; i < _i; i++) {
+            const id = this.ids[i];
+            const parent = this.parent.unitMap.get(id);
+
+            let unit: ArrayLike<number> = this.unitMap.get(id).array;
+
+            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;
+                symmGroups.add(parent.id, parent);
+                continue;
+            }
+
+            if (l > 1) sortArray(unit);
+
+            let child = parent.getChild(SortedArray.ofSortedArray(unit));
+            const pivot = symmGroups.add(child.id, child);
+            if (child !== pivot) child = pivot.applyOperator(child.id, child.conformation.operator, true);
+            newUnits[newUnits.length] = child;
+        }
+
+        return Structure.create(newUnits);
+    }
+
+    get isEmpty() {
+        return this.elementCount === 0;
+    }
+
+    constructor(private parent: Structure) {
+
+    }
+}
diff --git a/src/mol-task/execution/observable.ts b/src/mol-task/execution/observable.ts
index 82d4e05a43917365d625115cdd1b13c072346d88..81fe04efb346a3ba3d125761e8487299826077cd 100644
--- a/src/mol-task/execution/observable.ts
+++ b/src/mol-task/execution/observable.ts
@@ -10,13 +10,26 @@ import { Progress } from './progress'
 import { now } from '../util/now'
 import { Scheduler } from '../util/scheduler'
 
-function ExecuteObservable<T>(task: Task<T>, observer: Progress.Observer, updateRateMs = 250) {
+interface ExposedTask<T> extends Task<T> {
+    f: (ctx: RuntimeContext) => Promise<T>,
+    onAbort?: () => void
+}
+
+export function ExecuteObservable<T>(task: Task<T>, observer: Progress.Observer, updateRateMs = 250) {
     const info = ProgressInfo(task, observer, updateRateMs);
     const ctx = new ObservableRuntimeContext(info, info.root);
-    return execute(task, ctx);
+    return execute(task as ExposedTask<T>, ctx);
+}
+
+export function ExecuteInContext<T>(ctx: RuntimeContext, task: Task<T>) {
+    return execute(task as ExposedTask<T>, ctx as ObservableRuntimeContext);
 }
 
-namespace ExecuteObservable {
+export function ExecuteObservableChild<T>(ctx: RuntimeContext, task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>) {
+    return (ctx as ObservableRuntimeContext).runChild(task, progress);
+}
+
+export namespace ExecuteObservable {
     export let PRINT_ERRORS_TO_STD_ERR = false;
 }
 
@@ -77,10 +90,10 @@ function snapshotProgress(info: ProgressInfo): Progress {
     return { root: cloneTree(info.root), canAbort: canAbort(info.root), requestAbort: info.tryAbort };
 }
 
-async function execute<T>(task: Task<T>, ctx: ObservableRuntimeContext) {
+async function execute<T>(task: ExposedTask<T>, ctx: ObservableRuntimeContext) {
     ctx.node.progress.startedTime = now();
     try {
-        const ret = await task.__f(ctx);
+        const ret = await task.f(ctx);
         if (ctx.info.abortToken.abortRequested) {
             abort(ctx.info, ctx.node);
         }
@@ -91,7 +104,7 @@ async function execute<T>(task: Task<T>, ctx: ObservableRuntimeContext) {
             if (ctx.node.children.length > 0) {
                 await new Promise(res => { ctx.onChildrenFinished = res; });
             }
-            if (task.__onAbort) task.__onAbort();
+            if (task.onAbort) task.onAbort();
         }
         if (ExecuteObservable.PRINT_ERRORS_TO_STD_ERR) console.error(e);
         throw e;
@@ -197,7 +210,7 @@ class ObservableRuntimeContext implements RuntimeContext {
         children.push(node);
         const ctx = new ObservableRuntimeContext(this.info, node);
         try {
-            return await execute(task, ctx);
+            return await execute(task as ExposedTask<T>, ctx);
         } catch (e) {
             if (Task.isAbort(e)) {
                 // need to catch the error here because otherwise
@@ -223,6 +236,4 @@ class ObservableRuntimeContext implements RuntimeContext {
         this.node = node;
         this.info = info;
     }
-}
-
-export { ExecuteObservable }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-task/execution/runtime-context.ts b/src/mol-task/execution/runtime-context.ts
index d2529a3eb3599d9ece65c7183b7a36cdc0117b33..d0469abcfe65c408f321087e40170791c19c4176 100644
--- a/src/mol-task/execution/runtime-context.ts
+++ b/src/mol-task/execution/runtime-context.ts
@@ -4,8 +4,6 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task } from '../task'
-
 interface RuntimeContext {
     readonly shouldUpdate: boolean,
     readonly isSynchronous: boolean,
@@ -15,11 +13,7 @@ interface RuntimeContext {
     //
     // Alternatively, progress can be updated without notifying (and yielding) using update(progress, true).
     // This is useful for nested tasks.
-    update(progress?: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void,
-
-    // Run a child task that adds a new node to the progress tree.
-    // Allow to pass the progress so that the progress tree can be kept in a "good state" without having to separately call update.
-    runChild<T>(task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T>
+    update(progress?: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void
 }
 
 namespace RuntimeContext {
diff --git a/src/mol-task/execution/synchronous.ts b/src/mol-task/execution/synchronous.ts
index 4ca40a04a5ccc91024b08ca3bcf6093ac8643e34..5c729a61097f58d47d00619901f00487dcc282df 100644
--- a/src/mol-task/execution/synchronous.ts
+++ b/src/mol-task/execution/synchronous.ts
@@ -4,21 +4,12 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task } from '../task'
 import { RuntimeContext } from './runtime-context'
 
 class SynchronousRuntimeContext implements RuntimeContext {
     shouldUpdate = false;
     isSynchronous = true;
-
     update(progress: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void { }
-    runChild<T>(task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T> { return ExecuteSynchronous(task); }
-}
-
-const SyncRuntimeInstance = new SynchronousRuntimeContext();
-
-function ExecuteSynchronous<T>(task: Task<T>) {
-    return task.__f(SyncRuntimeInstance);
 }
 
-export { ExecuteSynchronous }
\ No newline at end of file
+export const SyncRuntimeContext = new SynchronousRuntimeContext();
\ No newline at end of file
diff --git a/src/mol-task/index.ts b/src/mol-task/index.ts
index 830153c1cfaa3cd6512b083dd988b90b825bdb08..2ef6489c9fffe85d67739274d1aab599dae14de9 100644
--- a/src/mol-task/index.ts
+++ b/src/mol-task/index.ts
@@ -6,21 +6,10 @@
 
 import { Task } from './task'
 import { RuntimeContext } from './execution/runtime-context'
-import { ExecuteSynchronous } from './execution/synchronous'
-import { ExecuteObservable } from './execution/observable'
 import { Progress } from './execution/progress'
 import { now } from './util/now'
 import { Scheduler } from './util/scheduler'
 import { MultistepTask } from './util/multistep'
 import { chunkedSubtask } from './util/chunked'
 
-// Run the task without the ability to observe its progress.
-function Run<T>(task: Task<T>): Promise<T>;
-// Run the task with the ability to observe its progress and request cancellation.
-function Run<T>(task: Task<T>, observer: Progress.Observer, updateRateMs?: number): Promise<T>;
-function Run<T>(task: Task<T>, observer?: Progress.Observer, updateRateMs?: number): Promise<T> {
-    if (observer) return ExecuteObservable(task, observer, updateRateMs || 250);
-    return ExecuteSynchronous(task);
-}
-
-export { Task, RuntimeContext, Progress, Run, now, Scheduler, MultistepTask, chunkedSubtask }
\ No newline at end of file
+export { Task, RuntimeContext, Progress, now, Scheduler, MultistepTask, chunkedSubtask }
\ No newline at end of file
diff --git a/src/mol-task/task.ts b/src/mol-task/task.ts
index 0ce5ca40bf65ef59de655b3ecdf39d4a2b12abec..ee5474fa9175064d06a0ed97e0e4fa9230de8ca5 100644
--- a/src/mol-task/task.ts
+++ b/src/mol-task/task.ts
@@ -5,25 +5,59 @@
  */
 
 import { RuntimeContext } from './execution/runtime-context'
+import { Progress } from './execution/progress'
+import { ExecuteObservable, ExecuteObservableChild, ExecuteInContext } from './execution/observable';
+import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
 
 // A "named function wrapper" with built in "computation tree progress tracking".
 // Use Run(t, ?observer, ?updateRate) to execute
 interface Task<T> {
+    // run the task without observation
+    run(): Promise<T>,
+    // run the task with the specified observer, default updateRate is 250ms
+    run(observer: Progress.Observer, updateRateMs?: number): Promise<T>,
+
+    // Run a child task that adds a new node to the progress tree.
+    // Allow to pass the progress so that the progress tree can be kept in a "good state" without having to separately call update.
+    runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T>
+
+    // Run the task on the specified context.
+    runInContext(ctx: RuntimeContext): Promise<T>
+
     readonly id: number,
-    readonly name: string,
-    // Do not call this directly, use Run.
-    readonly __f: (ctx: RuntimeContext) => Promise<T>,
-    // Do not call this directly, use Run.
-    readonly __onAbort: (() => void) | undefined
+    readonly name: string
 }
 
 namespace Task {
+    class Impl<T> implements Task<T> {
+        readonly id: number;
+
+        run(observer?: Progress.Observer, updateRateMs?: number): Promise<T> {
+            if (observer) return ExecuteObservable(this, observer, updateRateMs as number || 250);
+            return this.f(SyncRuntimeContext);
+        }
+
+        runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T> {
+            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
+            return ExecuteObservableChild(ctx, this, progress as string | Partial<RuntimeContext.ProgressUpdate>);
+        }
+
+        runInContext(ctx: RuntimeContext): Promise<T> {
+            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
+            return ExecuteInContext(ctx, this);
+        }
+
+        constructor(public name: string, public f: (ctx: RuntimeContext) => Promise<T>, public onAbort?: () => void) {
+            this.id = nextId();
+        }
+    }
+
     export interface Aborted { isAborted: true, reason: string }
     export function isAbort(e: any): e is Aborted { return !!e && !!e.isAborted; }
     export function Aborted(reason: string): Aborted { return { isAborted: true, reason }; }
 
     export function create<T>(name: string, f: (ctx: RuntimeContext) => Promise<T>, onAbort?: () => void): Task<T> {
-        return { id: nextId(), name, __f: f, __onAbort: onAbort };
+        return new Impl(name, f, onAbort);
     }
 
     export function constant<T>(name: string, value: T): Task<T> { return create(name, async ctx => value); }
diff --git a/src/mol-util/bit-flags.ts b/src/mol-util/bit-flags.ts
index 0cf3b761d5c4297081fab1eb9cd7c340e80fe99b..8143eb160bcbc0a1268cb3ddb9ef56d094d2d6e8 100644
--- a/src/mol-util/bit-flags.ts
+++ b/src/mol-util/bit-flags.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-interface BitFlags<Flags> { '@type': Flags }
+interface BitFlags<Flags> extends Number { '@type': Flags }
 
 namespace BitFlags {
     export function create<F>(flags: F): BitFlags<F> { return flags as any; }
diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts
index fe02007907f7b1dbbf84790599ec9c25b7e5edaa..0389fc0646d088a592c9292fb36e813397373aa6 100644
--- a/src/mol-view/state/transform.ts
+++ b/src/mol-view/state/transform.ts
@@ -6,12 +6,12 @@
 
 import CIF from 'mol-io/reader/cif'
 import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity } from './entity';
-import { Run } from 'mol-task';
-import { Model, Structure, Symmetry } from 'mol-model/structure';
+import { Model, Structure } from 'mol-model/structure';
 
 import { StateContext } from './context';
 import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { StructureRepresentation } from 'mol-geo/representation/structure';
+import StructureSymmetry from 'mol-model/structure/structure/symmetry';
 
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
 
@@ -46,7 +46,7 @@ export type DataToCif = StateTransform<DataEntity, CifEntity, {}>
 export const DataToCif: DataToCif = StateTransform.create('data', 'cif', 'data-to-cif',
     async function (ctx: StateContext, dataEntity: DataEntity) {
         const comp = CIF.parse(dataEntity.value.data)
-        const parsed = await Run(comp, ctx.log)
+        const parsed = await comp.run(ctx.log)
         if (parsed.isError) throw parsed
         return CifEntity.ofCifFile(ctx, parsed.result)
     })
@@ -60,7 +60,8 @@ export const CifToMmcif: CifToMmcif = StateTransform.create('cif', 'mmcif', 'cif
 export type MmcifToModel = StateTransform<MmcifEntity, ModelEntity, {}>
 export const MmcifToModel: MmcifToModel = StateTransform.create('mmcif', 'model', 'mmcif-to-model',
     async function (ctx: StateContext, mmcifEntity: MmcifEntity) {
-        return ModelEntity.ofModels(ctx, Model.create({ kind: 'mmCIF', data: mmcifEntity.value }))
+        const models = await Model.create({ kind: 'mmCIF', data: mmcifEntity.value }).run(ctx.log)
+        return ModelEntity.ofModels(ctx, models)
     })
 
 export interface StructureProps {
@@ -75,7 +76,7 @@ export const ModelToStructure: ModelToStructure = StateTransform.create('model',
         let structure: Structure
         const assemblies = model.symmetry.assemblies
         if (assemblies.length) {
-            structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), ctx.log)
+            structure = await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly || '1').run(ctx.log)
         } else {
             structure = Structure.ofModel(model)
         }
@@ -86,7 +87,7 @@ export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEnti
 export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill',
     async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) {
         const spacefillRepr = StructureRepresentation(Spacefill)
-        await Run(spacefillRepr.create(structureEntity.value, props), ctx.log)
+        await spacefillRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(spacefillRepr)
         ctx.viewer.requestDraw()
         console.log(ctx.viewer.stats)
@@ -98,7 +99,7 @@ export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill
     async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) {
         console.log('fopbar')
         const spacefillRepr = spacefillEntity.value
-        await Run(spacefillRepr.update(props), ctx.log)
+        await spacefillRepr.update(props).run(ctx.log)
         ctx.viewer.add(spacefillRepr)
         ctx.viewer.update()
         ctx.viewer.requestDraw()
diff --git a/src/mol-view/util.ts b/src/mol-view/util.ts
index cf9343d2450dfca85f2045008d054f939240c0eb..0fa3e82b42a181d4d3dd9f77aabce3007c2c87b8 100644
--- a/src/mol-view/util.ts
+++ b/src/mol-view/util.ts
@@ -5,7 +5,7 @@
  */
 
 import CIF from 'mol-io/reader/cif'
-import { Run, Progress } from 'mol-task'
+import { Progress } from 'mol-task'
 import { VolumeData, parseDensityServerData } from 'mol-model/volume'
 import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
 
@@ -16,7 +16,7 @@ export async function downloadCif(url: string, isBinary: boolean) {
 
 export async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data)
-    const parsed = await Run(comp, Progress.format);
+    const parsed = await comp.run(Progress.format);
     if (parsed.isError) throw parsed;
     return parsed.result
 }
@@ -26,7 +26,7 @@ export type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
 export async function getVolumeFromEmdId(emdid: string): Promise<Volume> {
     const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true)
     const data = CIF.schema.densityServer(cif.blocks[1])
-    return { source: data, volume: await Run(parseDensityServerData(data)) }
+    return { source: data, volume: await parseDensityServerData(data).run() }
 }
 
 export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
diff --git a/src/perf-tests/lookup3d.ts b/src/perf-tests/lookup3d.ts
index 3875a8c138aa79364204574cebb6b97d2c9a17b0..3b52eee9f0e027d48f6113ae3551a3d42e922038 100644
--- a/src/perf-tests/lookup3d.ts
+++ b/src/perf-tests/lookup3d.ts
@@ -4,7 +4,6 @@ import CIF from 'mol-io/reader/cif'
 
 import { Structure, Model } from 'mol-model/structure'
 
-import { Run } from 'mol-task';
 import { GridLookup3D } from 'mol-math/geometry';
 // import { sortArray } from 'mol-data/util';
 import { OrderedSet } from 'mol-data/int';
@@ -27,14 +26,14 @@ async function readData(path: string) {
 export async function readCIF(path: string) {
     const input = await readData(path)
     const comp = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input);
-    const parsed = await Run(comp);
+    const parsed = await comp.run();
     if (parsed.isError) {
         throw parsed;
     }
 
     const data = parsed.result.blocks[0];
     const mmcif = CIF.schema.mmCIF(data);
-    const models = Model.create({ kind: 'mmCIF', data: mmcif });
+    const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
     const structures = models.map(Structure.ofModel);
 
     return { mmcif, models, structures };
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index fc582759279cf4bf2dd28705a5b9fc5663cf7b71..bd38cf542c8bd4b26050d3bf7d6b9a836af3574d 100644
--- a/src/perf-tests/sets.ts
+++ b/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) {
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 03f04a2333d8fcfcc8af2b31f9b2b7cc02036255..b2fea5b987fa7e53b58545ada38256da9a355dea 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -11,12 +11,13 @@ 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, StructureSymmetry, 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 { Vec3 } from 'mol-math/linear-algebra';
+//import { printUnits } from 'apps/structure-info/model';
+//import { EquivalenceClasses } from 'mol-data/util';
 
 require('util.promisify').shim();
 const readFileAsync = util.promisify(fs.readFile);
@@ -61,7 +62,7 @@ export async function readCIF(path: string) {
 
     console.time('parse');
     const comp = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input);
-    const parsed = await Run(comp);
+    const parsed = await comp.run();
     console.timeEnd('parse');
     if (parsed.isError) {
         throw parsed;
@@ -73,7 +74,7 @@ export async function readCIF(path: string) {
 
     console.timeEnd('schema')
     console.time('buildModels')
-    const models = Model.create({ kind: 'mmCIF', data: mmcif });
+    const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
     console.timeEnd('buildModels')
     const structures = models.map(Structure.ofModel);
 
@@ -119,19 +120,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 +135,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 +195,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;
@@ -296,51 +292,107 @@ export namespace PropertyAccess {
 
     export async function testAssembly(id: string, s: Structure) {
         console.time('assembly')
-        const a = await Run(Symmetry.buildAssembly(s, '1'));
+        const a = await StructureSymmetry.buildAssembly(s, '1').run();
+        //const auth_comp_id = Q.props.residue.auth_comp_id;
+        //const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
+        //const alas = await query(q1, a);
+
         console.timeEnd('assembly')
         fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, a, true));
+        //fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, Selection.unionStructure(alas), true));
         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');
-
-        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))
-        );
+    export async function testSymmetry(id: string, s: Structure) {
+        console.time('symmetry')
+        const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)).run();
+        //const auth_comp_id = Q.props.residue.auth_comp_id;
+        //const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
+        //const alas = await query(q1, a);
 
-        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.timeEnd('symmetry')
+        fs.writeFileSync(`${DATA_DIR}/${id}_symm.bcif`, to_mmCIF(id, a, true));
+        //fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, Selection.unionStructure(alas), true));
+        console.log('exported');
+    }
 
-        console.log('group count', uniqueGroups.groups.length);
+    export async function testIncludeSurroundings(id: string, s: Structure) {
+        //const a = s; 
+        console.time('symmetry')
+        const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-2, -2, -2), Vec3.create(2, 2, 2)).run();
+        //console.log(printUnits(a));
+
+        const auth_comp_id = Q.props.residue.auth_comp_id, op = Q.props.unit.operator_name;
+        //const q1 = Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'REA' });
+        const q1 = Q.modifiers.includeSurroundings(Q.generators.atoms({
+            chainTest: l => op(l) === '1_555',
+            residueTest: l => auth_comp_id(l) === 'REA'
+        }), {
+            radius: 5,
+            wholeResidues: true
+        });
+        const surr = Selection.unionStructure(await query(Query(q1), a));
+        console.timeEnd('symmetry')
+
+        // for (const u of surr.units) {
+        //     const { atomId } = u.model.atomicConformation;
+        //     console.log(`${u.id}, ${u.conformation.operator.name}`);
+        //     for (let i = 0; i < u.elements.length; i++) {
+        //         console.log(`  ${atomId.value(u.elements[i])}`);
+        //     }
+        // }
+
+        // const it = surr.elementLocations();
+        // while (it.hasNext) {
+        //     const e = it.move();
+        //     console.log(`${Q.props.unit.operator_name(e)} ${Q.props.atom.id(e)}`);
+        // }
+    //fs.writeFileSync(`${DATA_DIR}/${id}_surr.bcif`, to_mmCIF(id, a, true));
+        fs.writeFileSync(`${DATA_DIR}/${id}_surr.cif`, to_mmCIF(id, surr, false));
+        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');
+
+    //     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 });
+    //     }
+
+    //     console.log('group count', uniqueGroups.groups.length);
+    // }
+
     function query(q: Query, s: Structure) {
-        return Run((q(s)));
+        return q(s).run();
     }
 
     export async function run() {
         //const { structures, models/*, mmcif*/ } = await getBcif('1cbs');
         // 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, models /*, mmcif*/ } = await readCIF('e:/test/quick/1cbs_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]);
+        //await testSymmetry('1cbs', structures[0]);
+        await testIncludeSurroundings('1cbs', structures[0]);
         // throw '';
 
         // console.log(models[0].symmetry.assemblies);
@@ -356,11 +408,11 @@ export namespace PropertyAccess {
         // return;
 
         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('sp', sumProperty(structures[0], l => l.unit.model.atomicConformation.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 +455,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].atomicConformation.atomId.value;
         const suite = new B.Suite();
         suite
             //.add('test q', () => q1(structures[0]))
             //.add('test q', () => q(structures[0]))
-            .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('test int', () => sumProperty(structures[0], l => col(l.element)))
+            .add('test q1', async () => await query(q1, structures[0]))
+            .add('test q3', async () => await query(q3, structures[0]))
             // .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]))