From ba2a4f3a99ae98b4b9dae3ee9f476f290f67d056 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Thu, 10 May 2018 16:10:54 +0200
Subject: [PATCH] More refactoring of mol-model

---
 package-lock.json                             | Bin 478290 -> 489768 bytes
 package.json                                  |   2 +-
 src/apps/structure-info/model.ts              |  12 +-
 .../representation/structure/spacefill.ts     |   2 +-
 src/mol-geo/theme/structure/color/chain-id.ts |  10 +-
 .../theme/structure/color/element-symbol.ts   |   2 +-
 src/mol-geo/theme/structure/size/element.ts   |   2 +-
 src/mol-math/linear-algebra/3d/mat3.ts        |   4 +
 src/mol-math/linear-algebra/tensor.ts         |  12 +-
 src/mol-model/structure/export/mmcif.ts       |   2 +-
 src/mol-model/structure/model/format.ts       |   6 +-
 src/mol-model/structure/model/formats/gro.ts  | 307 +++++++++---------
 .../structure/model/formats/mmcif.ts          |  52 +--
 .../structure/model/formats/mmcif/assembly.ts |   2 +-
 .../structure/model/formats/mmcif/bonds.ts    |   2 +-
 .../structure/model/formats/mmcif/ihm.ts      |  87 +++--
 .../structure/model/formats/mmcif/util.ts     |   4 +-
 src/mol-model/structure/model/model.ts        |  26 +-
 .../model/properties/atomic/hierarchy.ts      |  17 +-
 .../properties/coarse-grained/conformation.ts |   0
 .../properties/coarse-grained/hierarchy.ts    |  53 ---
 .../structure/model/properties/coarse.ts      |   8 +
 .../model/properties/coarse/conformation.ts   |  30 ++
 .../model/properties/coarse/hierarchy.ts      |  49 +++
 .../structure/model/properties/common.ts      |  16 -
 .../utils/atomic-keys.ts}                     |  17 +-
 .../model/properties/utils/coarse-keys.ts     |  84 +++++
 .../{ => properties}/utils/guess-element.ts   |   0
 src/mol-model/structure/query/generators.ts   |   8 +-
 src/mol-model/structure/query/properties.ts   |  66 ++--
 .../structure/structure/structure.ts          |  19 +-
 src/mol-model/structure/structure/unit.ts     |  54 +--
 .../structure/unit/bonds/intra-compute.ts     |   6 +-
 src/perf-tests/structure.ts                   |   4 +-
 34 files changed, 560 insertions(+), 405 deletions(-)
 delete mode 100644 src/mol-model/structure/model/properties/coarse-grained/conformation.ts
 delete mode 100644 src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts
 create mode 100644 src/mol-model/structure/model/properties/coarse.ts
 create mode 100644 src/mol-model/structure/model/properties/coarse/conformation.ts
 create mode 100644 src/mol-model/structure/model/properties/coarse/hierarchy.ts
 rename src/mol-model/structure/model/{utils/hierarchy-keys.ts => properties/utils/atomic-keys.ts} (90%)
 create mode 100644 src/mol-model/structure/model/properties/utils/coarse-keys.ts
 rename src/mol-model/structure/model/{ => properties}/utils/guess-element.ts (100%)

diff --git a/package-lock.json b/package-lock.json
index 2d93002f4e7b77be351e8e4eb333e62b3f5807b4..c1e108171140e7de69fdc590f15d660d717faef1 100644
GIT binary patch
delta 20444
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
K{=CNX8vh?(mT0a3

delta 20628
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

diff --git a/package.json b/package.json
index fc7b94070..23e18880b 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,7 @@
     "argparse": "^1.0.10",
     "express": "^4.16.3",
     "gl": "^4.0.4",
-    "material-ui": "^1.0.0-beta.46",
+    "material-ui": "~1.0.0-beta.43",
     "node-fetch": "^2.1.2",
     "react": "^16.3.2",
     "react-dom": "^16.3.2",
diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index 1bc3b500b..15ee55fd8 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -30,7 +30,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
@@ -92,10 +92,10 @@ export function printUnits(structure: Structure) {
         } else if (Unit.isCoarse(l.unit)) {
             console.log(`Coarse unit ${unit.id} (${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++) {
+            for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
                 l.element = OrderedSet.getAt(elements, j);
 
                 const residues: string[] = [];
@@ -104,16 +104,16 @@ 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) {
diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts
index 1946f8103..db8240514 100644
--- a/src/mol-geo/representation/structure/spacefill.ts
+++ b/src/mol-geo/representation/structure/spacefill.ts
@@ -39,7 +39,7 @@ function createSpacefillMesh(unit: Unit, detail: number) {
         if (Unit.isAtomic(unit)) {
             radius = Queries.props.atom.vdw_radius
         } else if (Unit.isSpheres(unit)) {
-            radius = Queries.props.coarse_grained.sphere_radius
+            radius = Queries.props.coarse.sphere_radius
         } else {
             console.warn('Unsupported unit type')
             return meshBuilder.getMesh()
diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts
index 95e305be3..d521d6036 100644
--- a/src/mol-geo/theme/structure/color/chain-id.ts
+++ b/src/mol-geo/theme/structure/color/chain-id.ts
@@ -18,11 +18,11 @@ function createChainIdMap(unit: Unit) {
     let count: number
     let asym_id: Column<string>
     if (Unit.isAtomic(unit)) {
-        asym_id = unit.model.hierarchy.chains.label_asym_id
-        count = unit.model.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.sites.asym_id
-        count = unit.sites.count
+        asym_id = unit.coarseElements.asym_id
+        count = unit.coarseElements.count
     } else {
         console.warn('Unknown unit type')
         return { map, count: index }
@@ -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()
diff --git a/src/mol-geo/theme/structure/color/element-symbol.ts b/src/mol-geo/theme/structure/color/element-symbol.ts
index 815985b33..affedfac0 100644
--- a/src/mol-geo/theme/structure/color/element-symbol.ts
+++ b/src/mol-geo/theme/structure/color/element-symbol.ts
@@ -23,7 +23,7 @@ export function elementSymbolColor(element: ElementSymbol): Color {
 
 export function elementSymbolColorData(props: StructureColorDataProps) {
     const { group: { units, elements }, vertexMap } = props
-    const { type_symbol } = units[0].model.hierarchy.atoms
+    const { type_symbol } = units[0].model.atomicHierarchy.atoms
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
             const e = elements[elementIdx]
diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/element.ts
index 8bf99acc0..741ba136f 100644
--- a/src/mol-geo/theme/structure/size/element.ts
+++ b/src/mol-geo/theme/structure/size/element.ts
@@ -17,7 +17,7 @@ export function elementSizeData(props: StructureSizeDataProps) {
     if (Unit.isAtomic(unit)) {
         radius = Queries.props.atom.vdw_radius
     } else if (Unit.isSpheres(unit)) {
-        radius = Queries.props.coarse_grained.sphere_radius
+        radius = Queries.props.coarse.sphere_radius
     }
     const l = Element.Location()
     l.unit = unit
diff --git a/src/mol-math/linear-algebra/3d/mat3.ts b/src/mol-math/linear-algebra/3d/mat3.ts
index cd577776b..c72dc713d 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/tensor.ts b/src/mol-math/linear-algebra/tensor.ts
index 976e410e1..4bcae46f7 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/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index 65ded0cbb..d7f4491a6 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -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)
     ]
 };
 
diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts
index d463c30d7..2fa1f8ae1 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 { File 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 46a9cd165..5e5350974 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 { Atoms } from 'mol-io/reader/gro/schema'
-import Format from '../format'
-import Model from '../model'
-import { AtomicConformation, AtomicData, AtomsSchema, ResiduesSchema, ChainsSchema, AtomicSegments } from '../properties/atomic'
-import { CoarseGrainedHierarchy } from '../properties/coarse-grained/hierarchy'
-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';
-import { ModelSymmetry } from '../properties/symmetry';
-
-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.hierarchy, hierarchyData)) {
-        return {
-            ...previous,
-            atomSiteConformation: 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 = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
-    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
-    return {
-        id: UUID.create(),
-        sourceData: format,
-        modelNum,
-        hierarchy,
-        entities,
-        sequence: Sequence.fromAtomicHierarchy(hierarchy),
-        atomSiteConformation: getConformation(structure.atoms),
-        coarseGrained: CoarseGrainedHierarchy.Empty,
-        symmetry: ModelSymmetry.Default,
-        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;
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index 22644e2fa..3f68d5444 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -4,23 +4,23 @@
  * @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 { AtomsSchema, ResiduesSchema, ChainsSchema, AtomicData, AtomicSegments, AtomicConformation } from '../properties/atomic'
-import { ModelSymmetry } from '../properties/symmetry'
-import findHierarchyKeys from '../utils/hierarchy-keys'
-import { ElementSymbol} from '../types'
-import createAssemblies from './mmcif/assembly'
-
-import mmCIF_Format = Format.mmCIF
-import { getSequence } from './mmcif/sequence';
-import { Entities } from '../properties/common';
-import { coarseGrainedFromIHM } from './mmcif/ihm';
+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
 
 function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
@@ -120,10 +120,10 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: 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)
         };
     }
 
@@ -134,21 +134,23 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
 
     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,
+        symmetry: getSymmetry(format)
     };
 }
 
diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts
index 2315133d4..48720596c 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 7bd6ec6f9..95ac32d4d 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds.ts
+++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts
@@ -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,
diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts
index 5eab89ab0..1dfdc1c1d 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 { CoarseGrainedHierarchy } from '../../properties/coarse-grained/hierarchy'
+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): CoarseGrainedHierarchy {
-    if (data.ihm_model_list._rowCount === 0) return CoarseGrainedHierarchy.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): CoarseGrainedHierarchy.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): CoarseGrainedHierarchy.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/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts
index a03d511f5..1817e1c22 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 eb7d5024a..0a1725d48 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -9,10 +9,10 @@ import Format from './format'
 import Sequence from './properties/sequence'
 import { AtomicHierarchy, AtomicConformation } from './properties/atomic'
 import { ModelSymmetry } from './properties/symmetry'
-import { CoarseGrainedHierarchy } from './properties/coarse-grained/hierarchy'
+import { CoarseHierarchy, CoarseConformation } from './properties/coarse'
 import { Entities } from './properties/common';
 
-import from_gro from './formats/gro'
+//import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
 
 /**
@@ -31,11 +31,11 @@ interface Model extends Readonly<{
     entities: Entities,
     sequence: Sequence,
 
-    hierarchy: AtomicHierarchy,
-    atomSiteConformation: AtomicConformation,
-    coarseGrained: CoarseGrainedHierarchy,
+    atomicHierarchy: AtomicHierarchy,
+    atomicConformation: AtomicConformation,
 
-    atomCount: number,
+    coarseHierarchy: CoarseHierarchy,
+    coarseConformation: CoarseConformation
 }> {
 
 } { }
@@ -43,22 +43,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/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index 02ef4dd5d..fe50e990b 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -8,7 +8,6 @@ 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 { Keys } from '../common';
 
 export const AtomsSchema = {
     type_symbol: Column.Schema.Aliased<ElementSymbol>(mmCIF.atom_site.type_symbol),
@@ -52,9 +51,21 @@ export interface AtomicSegments {
     chainSegments: Segmentation
 }
 
-export interface AtomicKeys extends 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: ArrayLike<number>,
+    // assigne a key to each chain index
+    // also index to the Entities table.
+    entityKey: ArrayLike<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
 }
 
diff --git a/src/mol-model/structure/model/properties/coarse-grained/conformation.ts b/src/mol-model/structure/model/properties/coarse-grained/conformation.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts b/src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts
deleted file mode 100644
index 12dd042cd..000000000
--- a/src/mol-model/structure/model/properties/coarse-grained/hierarchy.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 CoarseGrainedHierarchy {
-    isDefined: boolean,
-    modelList: mmCIF['ihm_model_list'],
-    spheres: CoarseGrainedHierarchy.Spheres,
-    gaussians: CoarseGrainedHierarchy.Gaussians
-}
-
-namespace CoarseGrainedHierarchy {
-    export const Empty: CoarseGrainedHierarchy = { 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 SitesBase =  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 { CoarseGrainedHierarchy }
\ 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 000000000..d1fc3decf
--- /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 000000000..4298b80bd
--- /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 000000000..fedb463c9
--- /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 e70712cbb..9dadbc95a 100644
--- a/src/mol-model/structure/model/properties/common.ts
+++ b/src/mol-model/structure/model/properties/common.ts
@@ -5,24 +5,8 @@
  */
 
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-import { Column } from 'mol-data/db';
 
 export interface Entities {
     data: mmCIF['entity'],
     getEntityIndex(id: string): number
-}
-
-export interface Keys {
-    // 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 chain index
-    chainKey: Column<number>,
-    // assigne a key to each chain index
-    // also index to the Entities table.
-    entityKey: Column<number>,
-
-    findChainKey(entityId: string, label_asym_id: string): number
 }
\ 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 90%
rename from src/mol-model/structure/model/utils/hierarchy-keys.ts
rename to src/mol-model/structure/model/properties/utils/atomic-keys.ts
index aabfe1b4a..1cfec2f03 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 { AtomicData, AtomicSegments, AtomicKeys } from '../properties/atomic'
+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}`;
@@ -62,7 +61,7 @@ function missingEntity(k: string) {
     throw new Error(`Missing entity entry for entity id '${k}'.`);
 }
 
-function create(data: AtomicData, entities: Entities, segments: AtomicSegments): AtomicKeys {
+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: AtomicData, entities: Entities, segments: AtomicSegments):
 
     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 000000000..6ef72039a
--- /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/query/generators.ts b/src/mol-model/structure/query/generators.ts
index ac9df8509..01445a1da 100644
--- a/src/mol-model/structure/query/generators.ts
+++ b/src/mol-model/structure/query/generators.ts
@@ -80,8 +80,8 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
             const elements = unit.elements;
 
             builder.beginUnit(unit.id);
-            const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements);
-            const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements);
+            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(elements, chainSegment.start);
@@ -174,8 +174,8 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
             l.unit = unit;
             const elements = unit.elements;
 
-            const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements);
-            const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements);
+            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(elements, chainSegment.start);
diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts
index fc2a066ad..a4d4ad5b3 100644
--- a/src/mol-model/structure/query/properties.ts
+++ b/src/mol-model/structure/query/properties.ts
@@ -29,61 +29,61 @@ const atom = {
     x: Element.property(l => l.unit.conformation.x(l.element)),
     y: Element.property(l => l.unit.conformation.y(l.element)),
     z: Element.property(l => l.unit.conformation.z(l.element)),
-    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomSiteConformation.atomId.value(l.element)),
-    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomSiteConformation.occupancy.value(l.element)),
-    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomSiteConformation.B_iso_or_equiv.value(l.element)),
+    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.model.hierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
+    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.model.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.model.hierarchy.residueKey.value(l.unit.residueIndex[l.element])),
-
-    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
-    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
-    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
-    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
-    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element]))
+    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]))
 }
 
 const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.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.model.hierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
-    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
-    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
+    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 = {
+const coarse = {
     key: atom.key,
-    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.modelKey[l.element]),
-    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.entityKey[l.element]),
+    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.sites.asym_id.value(l.element)),
-    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.seq_id_begin.value(l.element)),
-    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.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.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.sites.radius.value(l.element)),
-    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.sites.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.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.sites.weight.value(l.element)),
-    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.sites.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.model.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,
@@ -112,7 +112,7 @@ const Properties = {
     chain,
     entity,
     unit,
-    coarse_grained
+    coarse
 }
 
 type Properties = typeof Properties
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index c2a2ecd38..efe4e8bde 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -13,6 +13,7 @@ import Element from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
 import StructureSymmetry from './symmetry';
+import { CoarseElements } from '../model/properties/coarse';
 
 class Structure {
     readonly unitMap: IntMap<Unit>;
@@ -90,7 +91,7 @@ namespace Structure {
     }
 
     export function ofModel(model: Model): Structure {
-        const chains = model.hierarchy.chainSegments;
+        const chains = model.atomicHierarchy.chainSegments;
         const builder = new StructureBuilder();
 
         for (let c = 0; c < chains.count; c++) {
@@ -98,21 +99,27 @@ namespace Structure {
             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 elements = SortedArray.ofBounds(0, cs.spheres.count);
-                builder.addUnit(Unit.Kind.Spheres, model, SymmetryOperator.Default, elements);
+                addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
             }
             if (cs.gaussians.count > 0) {
-                const elements = SortedArray.ofBounds(0, cs.spheres.count);
-                builder.addUnit(Unit.Kind.Gaussians, model, SymmetryOperator.Default, elements);
+                addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
             }
         }
 
         return builder.getStructure();
     }
 
+    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);
+        }
+    }
+
     export class StructureBuilder {
         private units: Unit[] = [];
 
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index 7ede3f8a7..4ea70f014 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -7,10 +7,10 @@
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { Model } from '../model'
 import { GridLookup3D, Lookup3D } from 'mol-math/geometry'
-import { CoarseGrainedHierarchy } from '../model/properties/coarse-grained/hierarchy';
 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';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -26,9 +26,9 @@ namespace Unit {
 
     export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
         switch (kind) {
-            case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
-            case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, model.coarseGrained.spheres, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
-            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, model.coarseGrained.gaussians, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation));
+            case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation));
+            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));
         }
     }
 
@@ -78,13 +78,13 @@ namespace Unit {
 
         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.atomSiteConformation));
+            return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation));
         }
 
         private _lookup3d?: Lookup3D = void 0;
         get lookup3d() {
             if (this._lookup3d) return this._lookup3d;
-            const { x, y, z } = this.model.atomSiteConformation;
+            const { x, y, z } = this.model.atomicConformation;
             this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements });
             return this._lookup3d;
         }
@@ -103,62 +103,64 @@ namespace Unit {
             this.elements = elements;
             this.conformation = conformation;
 
-            this.residueIndex = model.hierarchy.residueSegments.segmentMap;
-            this.chainIndex = model.hierarchy.chainSegments.segmentMap;
+            this.residueIndex = model.atomicHierarchy.residueSegments.segmentMap;
+            this.chainIndex = model.atomicHierarchy.chainSegments.segmentMap;
         }
     }
 
-    // Coarse grained representations.
-    export interface CoarseBase<S extends CoarseGrainedHierarchy.SitesBase> extends Base  {
-        readonly sites: S
-    }
-
-    class Coarse<S extends CoarseGrainedHierarchy.SitesBase> implements CoarseBase<S> {
-        readonly kind: Kind;
+    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 sites: S;
+
+        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, this.sites, elements, this.conformation);
+            return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation);
         }
 
         applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
             const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
-            return createCoarse(id, this.invariantId, this.model, this.kind, this.sites, this.elements, SymmetryOperator.createMapping(op, this.sites));
+            return createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements()));
         }
 
         private _lookup3d?: Lookup3D = void 0;
         get lookup3d() {
             if (this._lookup3d) return this._lookup3d;
-            const { x, y, z } = this.sites;
-            // TODO: support sphere radius
+            const { x, y, z } = this.getCoarseElements();
+            // TODO: support sphere radius?
             this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements });
             return this._lookup3d;
         }
 
-        constructor(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+        private getCoarseElements() {
+            return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
+        }
+
+        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.sites = sites;
+            this.coarseElements = kind === Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
+            this.coarseConformation = (kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians) as C;
         }
     }
 
-    function createCoarse<S extends CoarseGrainedHierarchy.SitesBase>(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
-        return new Coarse(id, invariantId, model, kind, sites, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
+    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 interface Spheres extends CoarseBase<CoarseGrainedHierarchy.Spheres> { kind: Kind.Spheres }
-    export interface Gaussians extends CoarseBase<CoarseGrainedHierarchy.Gaussians> { kind: Kind.Gaussians }
+    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/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
index 2d8f9af02..9cd4a9ed1 100644
--- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
+++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
@@ -109,11 +109,11 @@ function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[],
 function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds {
     const MAX_RADIUS = 3;
 
-    const { x, y, z } = unit.model.atomSiteConformation;
+    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.hierarchy.atoms;
-    const { label_comp_id } = unit.model.hierarchy.residues;
+    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
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 4070cca0b..92831adec 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -371,7 +371,7 @@ 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('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));
@@ -418,7 +418,7 @@ export namespace PropertyAccess {
         console.log(Selection.structureCount(q2r));
         //console.log(q1(structures[0]));
 
-        const col = models[0].atomSiteConformation.atomId.value;
+        const col = models[0].atomicConformation.atomId.value;
         const suite = new B.Suite();
         suite
             //.add('test q', () => q1(structures[0]))
-- 
GitLab