Browse Source

Merge branch 'master' into gl-new

# Conflicts:
#	src/mol-model/structure/query/properties.ts
Alexander Rose 7 years ago
parent
commit
bb81285b44

+ 5 - 0
data/mmcif-field-names.csv

@@ -58,6 +58,11 @@ entity.pdbx_mutation
 entity.pdbx_fragment
 entity.pdbx_ec
 
+entity_poly_seq.entity_id
+entity_poly_seq.num
+entity_poly_seq.mon_id
+entity_poly_seq.hetero
+
 entry.id
 
 exptl.entry_id

+ 1 - 1
src/apps/domain-annotation-server/mapping.ts

@@ -112,6 +112,6 @@ function getDomain(name: string, schema: any, allData: any) {
     return domains.length > 0 ? {
         name,
         domains: Table.ofRows({ ...S.Base, ...schema }, domains),
-        mappings: Table.ofRows(S.mapping, mappings)
+        mappings: Table.ofRows<S.mapping>(S.mapping, mappings)
     } : void 0;
 }

+ 103 - 10
src/apps/structure-info/index.ts

@@ -5,14 +5,38 @@
  */
 
 import * as argparse from 'argparse'
+import * as util from 'util'
+import * as fs from 'fs'
 import fetch from 'node-fetch'
 require('util.promisify').shim();
 
 // import { Table } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
-import { Model, Structure, ElementSet, Unit, ElementGroup } from 'mol-model/structure'
+import { Model, Structure, Element, ElementSet, Unit, ElementGroup, 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';
+
+const readFileAsync = util.promisify(fs.readFile);
+
+async function readFile(path: string) {
+    if (path.match(/\.bcif$/)) {
+        const input = await readFileAsync(path)
+        const data = new Uint8Array(input.byteLength);
+        for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
+        return data;
+    } else {
+        return readFileAsync(path, 'utf8');
+    }
+}
+
+async function readCif(path: string) {
+    const data = await readFile(path);
+    const parsed = await parseCif(data);
+    return CIF.schema.mmCIF(parsed.result.blocks[0])
+}
 
 async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data);
@@ -39,7 +63,7 @@ export function atomLabel(model: Model, aI: number) {
 }
 
 
-function printBonds(structure: Structure) {
+export function printBonds(structure: Structure) {
     const { units, elements } = structure;
     const unitIds = ElementSet.unitIndices(elements);
 
@@ -67,24 +91,93 @@ function printBonds(structure: Structure) {
     }
 }
 
-async function run(pdb: string) {
-    const mmcif = await getPdb(pdb)
+export function printSequence(model: Model) {
+    console.log('Sequence\n=============');
+    const { byEntityKey } = model.sequence;
+    for (const key of Object.keys(byEntityKey)) {
+        const seq = byEntityKey[+key];
+        console.log(`${seq.entityId} (${seq.num.value(0)}, ${seq.num.value(seq.num.rowCount - 1)}) (${seq.compId.value(0)}, ${seq.compId.value(seq.compId.rowCount - 1)})`);
+        // for (let i = 0; i < seq.compId.rowCount; i++) {
+        //     console.log(`${seq.entityId} ${seq.num.value(i)} ${seq.compId.value(i)}`);
+        // }
+    }
+    console.log();
+}
+
+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);
+
+        if (Unit.isAtomic(l.unit)) {
+            console.log(`Atomic unit ${unitId}: ${size} elements`);
+        } else if (Unit.isCoarse(l.unit)) {
+            console.log(`Coarse unit ${unitId} (${l.unit.elementType === CoarseGrained.ElementType.Sphere ? 'spheres' : 'gaussians'}): ${size} elements.`);
+
+            const props = Queries.props.coarse_grained;
+            const seq = l.unit.model.sequence;
+
+            for (let j = 0, _j = Math.min(size, 10); j < _j; j++) {
+                l.element = OrderedSet.getAt(set, j);
+
+                const residues: string[] = [];
+                const start = props.seq_id_begin(l), end = props.seq_id_end(l);
+                const compId = seq.byEntityKey[props.entityKey(l)].compId.value;
+                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(`...`);
+        }
+    }
+}
+
+
+export function printIHMModels(model: Model) {
+    if (!model.coarseGrained.isDefined) return false;
+    console.log('IHM Models\n=============');
+    console.log(Table.formatToString(model.coarseGrained.modelList));
+}
+
+async function run(mmcif: mmCIF_Database) {
     const models = Model.create({ kind: 'mmCIF', data: mmcif });
-    const structure = Structure.ofModel(models[0])
-    // console.log(structure)
-    printBonds(structure)
+    const structure = Structure.ofModel(models[0]);
+    printSequence(models[0]);
+    printIHMModels(models[0]);
+    printUnits(structure);
+}
+
+async function runDL(pdb: string) {
+    const mmcif = await getPdb(pdb)
+    run(mmcif);
+}
+
+async function runFile(filename: string) {
+    const mmcif = await readCif(filename);
+    run(mmcif);
 }
 
 const parser = new argparse.ArgumentParser({
   addHelp: true,
   description: 'Print info about a structure, mainly to test and showcase the mol-model module'
 });
-parser.addArgument([ '--pdb', '-p' ], {
+parser.addArgument([ '--download', '-d' ], {
     help: 'Pdb entry id'
 });
+parser.addArgument([ '--file', '-f' ], {
+    help: 'filename'
+});
 interface Args {
-    pdb: string
+    download?: string,
+    file?: string
 }
 const args: Args = parser.parseArgs();
 
-run(args.pdb)
+if (args.download) runDL(args.download)
+else if (args.file) runFile(args.file)

+ 14 - 1
src/mol-data/db/column.ts

@@ -132,6 +132,10 @@ namespace Column {
         return createFirstIndexMapOfColumn(column);
     }
 
+    export function createIndexer<T>(column: Column<T>) {
+        return createIndexerOfColumn(column);
+    }
+
     export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {
         return mapToArrayImpl<T, S>(column, f, ctor || Array);
     }
@@ -169,11 +173,20 @@ function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> {
     const map = new Map<T, number>();
     for (let i = 0, _i = c.rowCount; i < _i; i++) {
         const v = c.value(i);
-        if (!map.has(v)) return map.set(c.value(i), i);
+        if (!map.has(v)) map.set(c.value(i), i);
     }
     return map;
 }
 
+function createIndexerOfColumn<T>(c: Column<T>): (value: T) => number {
+    const map = new Map<T, number>();
+    for (let i = 0, _i = c.rowCount; i < _i; i++) {
+        const v = c.value(i);
+        if (!map.has(v)) map.set(c.value(i), i);
+    }
+    return v => map.has(v) ? map.get(v)! : -1;
+}
+
 function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schema: T, valueKind: Column.ValueKind): Column<T['T']> {
     const value: Column<T['T']>['value'] = row => v;
     return {

+ 35 - 2
src/mol-data/db/table.ts

@@ -6,6 +6,7 @@
 
 import Column from './column'
 import { sortArray } from '../util/sort'
+import { StringBuilder } from 'mol-util';
 
 /** A collection of columns */
 type Table<Schema extends Table.Schema> = {
@@ -58,7 +59,7 @@ namespace Table {
         return ret;
     }
 
-    export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Row<S>>): R {
+    export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
         const ret = Object.create(null);
         const rowCount = rows.length;
         const columns = Object.keys(schema);
@@ -83,7 +84,7 @@ namespace Table {
         ret._columns = columns;
         ret._schema = schema;
         for (const k of columns) {
-            (ret as any)[k] = Column.ofArray({ array: arrays[k], schema: schema[k] })
+            (ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
         }
         return ret as R;
     }
@@ -188,6 +189,38 @@ namespace Table {
         }
         return ret;
     }
+
+    export function formatToString<S extends Schema>(table: Table<S>) {
+        const sb = StringBuilder.create();
+
+        const { _columns: cols, _rowCount } = table;
+
+        let headerLength = 1;
+        StringBuilder.write(sb, '|');
+        for (let i = 0; i < cols.length; i++) {
+            StringBuilder.write(sb, cols[i]);
+            StringBuilder.write(sb, '|');
+            headerLength += cols[i].length + 1;
+        }
+        StringBuilder.newline(sb);
+        StringBuilder.write(sb, new Array(headerLength + 1).join('-'));
+        StringBuilder.newline(sb);
+
+        for (let r = 0; r < _rowCount; r++) {
+            StringBuilder.write(sb, '|');
+            for (let i = 0; i < cols.length; i++) {
+                const c = table[cols[i]];
+                if (c.valueKind(r) === Column.ValueKind.Present) {
+                    StringBuilder.write(sb, c.value(r));
+                    StringBuilder.write(sb, '|');
+                } else {
+                    StringBuilder.write(sb, '.|');
+                }
+            }
+            StringBuilder.newline(sb);
+        }
+        return StringBuilder.getString(sb);
+    }
 }
 
 export default Table

+ 1 - 1
src/mol-geo/representation/structure/point.ts

@@ -26,7 +26,7 @@ export type PointProps = Partial<typeof DefaultPointProps>
 export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
     const elementCount = OrderedSet.size(elementGroup.elements)
     const vertices = new Float32Array(elementCount * 3)
-    const { x, y, z } = unit.model.conformation
+    const { x, y, z } = unit.model.atomSiteConformation
     for (let i = 0; i < elementCount; i++) {
         const e = OrderedSet.getAt(elementGroup.elements, i)
         const i3 = i * 3

+ 1 - 1
src/mol-geo/representation/structure/spacefill.ts

@@ -32,7 +32,7 @@ function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: num
         const v = Vec3.zero()
         const m = Mat4.identity()
 
-        const { x, y, z } = unit.model.conformation
+        const { x, y, z } = unit.model.atomSiteConformation
         const { type_symbol } = unit.model.hierarchy.atoms
         const elementCount = OrderedSet.size(elementGroup.elements)
         for (let i = 0; i < elementCount; i++) {

+ 6 - 0
src/mol-io/reader/cif/schema/mmcif.ts

@@ -86,6 +86,12 @@ export const mmCIF_Schema = {
         pdbx_fragment: str,
         pdbx_ec: List(',', x => x),
     },
+    entity_poly_seq: {
+        entity_id: str,
+        num: int,
+        mon_id: str,
+        hetero: Aliased<'no' | 'n' | 'yes' | 'y'>(str)
+    },
     entry: {
         id: str,
     },

+ 4 - 4
src/mol-model/structure/export/mmcif.ts

@@ -102,10 +102,10 @@ const atom_site: Encoder.CategoryDefinition<Element.Location> = {
 
 function entityProvider({ model }: Context): Encoder.CategoryInstance {
     return {
-        data: model.hierarchy.entities,
-        definition: Encoder.CategoryDefinition.ofTable('entity', model.hierarchy.entities),
-        keys: () => Iterator.Range(0, model.hierarchy.entities._rowCount - 1),
-        rowCount: model.hierarchy.entities._rowCount
+        data: model.entities.data,
+        definition: Encoder.CategoryDefinition.ofTable('entity', model.entities.data),
+        keys: () => Iterator.Range(0, model.entities.data._rowCount - 1),
+        rowCount: model.entities.data._rowCount
     }
 }
 

+ 24 - 8
src/mol-model/structure/model/formats/gro.ts

@@ -11,13 +11,16 @@ import { Atoms } from 'mol-io/reader/gro/schema'
 import Format from '../format'
 import Model from '../model'
 import * as Hierarchy from '../properties/hierarchy'
-import Conformation from '../properties/conformation'
+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> }
 
@@ -70,12 +73,11 @@ function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Hiera
     // });
 
     const chains = Table.ofUndefinedColumns(Hierarchy.ChainsSchema, 0);
-    const entities = Table.ofUndefinedColumns(Hierarchy.EntitySchema, 0);
 
-    return { atoms, residues, chains, entities };
+    return { atoms, residues, chains };
 }
 
-function getConformation(atoms: Atoms): Conformation {
+function getConformation(atoms: Atoms): AtomSiteConformation {
     return {
         id: UUID.create(),
         atomId: atoms.atomNumber,
@@ -103,7 +105,7 @@ function createModel(format: gro_Format, modelNum: number, previous?: Model): Mo
     if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
         return {
             ...previous,
-            conformation: getConformation(structure.atoms)
+            atomSiteConformation: getConformation(structure.atoms)
         };
     }
 
@@ -111,13 +113,27 @@ function createModel(format: gro_Format, modelNum: number, previous?: Model): Mo
         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
     }
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, hierarchySegments);
+
+    // 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: { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments },
-        conformation: getConformation(structure.atoms),
+        hierarchy,
+        entities,
+        sequence: Sequence.fromHierarchy(hierarchy),
+        atomSiteConformation: getConformation(structure.atoms),
         coarseGrained: CoarseGrained.Empty,
         symmetry: { assemblies: [] },
         atomCount: structure.atoms.count

+ 28 - 11
src/mol-model/structure/model/formats/mmcif.ts

@@ -10,14 +10,16 @@ import { Interval, Segmentation } from 'mol-data/int'
 import Format from '../format'
 import Model from '../model'
 import * as Hierarchy from '../properties/hierarchy'
-import Conformation from '../properties/conformation'
-import CoarseGrained from '../properties/coarse-grained'
+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 mmCIF_Format = Format.mmCIF
+import { getSequence } from './mmcif/sequence';
+import { Entities } from '../properties/common';
+import { coarseGrainedFromIHM } from './mmcif/ihm';
 
 function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
@@ -29,6 +31,8 @@ function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
 }
 
 function findHierarchyOffsets({ data }: mmCIF_Format, bounds: Interval) {
+    if (Interval.size(bounds) === 0) return { residues: [], chains: [] };
+
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const residues = [start], chains = [start];
 
@@ -62,10 +66,10 @@ function createHierarchyData({ data }: mmCIF_Format, bounds: Interval, offsets:
     Table.columnToArray(residues, 'label_seq_id', Int32Array);
     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
     const chains = Table.view(atom_site, Hierarchy.ChainsSchema, offsets.chains);
-    return { atoms, residues, chains, entities: data.entity };
+    return { atoms, residues, chains };
 }
 
-function getConformation({ data }: mmCIF_Format, bounds: Interval): Conformation {
+function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomSiteConformation {
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const { atom_site } = data;
     return {
@@ -97,7 +101,7 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
     if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
         return {
             ...previous,
-            conformation: getConformation(format, bounds)
+            atomSiteConformation: getConformation(format, bounds)
         };
     }
 
@@ -105,25 +109,38 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
     }
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, hierarchySegments);
+
+    const entities: Entities = { data: format.data.entity, getEntityIndex: Column.createIndexer(format.data.entity.id) };
+
+    const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
+
+    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
+
     return {
         id: UUID.create(),
         sourceData: format,
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)),
-        hierarchy: { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments },
-        conformation: getConformation(format, bounds),
-        coarseGrained: CoarseGrained.Empty,
+        entities,
+        hierarchy,
+        sequence: getSequence(format.data, entities, hierarchy),
+        atomSiteConformation: getConformation(format, bounds),
+        coarseGrained: coarseGrainedFromIHM(format.data, entities),
         symmetry: getSymmetry(format),
         atomCount: Interval.size(bounds)
     };
 }
 
 function buildModels(format: mmCIF_Format): ReadonlyArray<Model> {
-    const models: Model[] = [];
     const atomCount = format.data.atom_site._rowCount;
+    const isIHM = format.data.ihm_model_list._rowCount > 0;
 
-    if (atomCount === 0) return models;
+    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);

+ 54 - 0
src/mol-model/structure/model/formats/mmcif/ihm.ts

@@ -0,0 +1,54 @@
+/**
+ * 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_Schema } from 'mol-io/reader/cif/schema/mmcif'
+import CoarseGrained from '../../properties/coarse-grained'
+import { Entities } from '../../properties/common';
+import { Column } from 'mol-data/db';
+
+function coarseGrainedFromIHM(data: mmCIF, entities: Entities): CoarseGrained {
+    if (data.ihm_model_list._rowCount === 0) return CoarseGrained.Empty;
+
+    const { ihm_model_list, ihm_sphere_obj_site, ihm_gaussian_obj_site } = data;
+    const modelIndex = Column.createIndexer(ihm_model_list.model_id);
+
+    return {
+        isDefined: true,
+        modelList: ihm_model_list,
+        spheres: getSpheres(ihm_sphere_obj_site, entities, modelIndex),
+        gaussians: getGaussians(ihm_gaussian_obj_site, entities, modelIndex)
+    };
+}
+
+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 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 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;
+
+    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
+    };
+}
+
+export { coarseGrainedFromIHM }

+ 32 - 0
src/mol-model/structure/model/formats/mmcif/sequence.ts

@@ -0,0 +1,32 @@
+/**
+ * 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 Sequence from '../../properties/sequence'
+import { Column } from 'mol-data/db';
+import { Hierarchy } from '../../properties/hierarchy';
+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);
+
+    const { entity_id, num, mon_id } = cif.entity_poly_seq;
+
+    const byEntityKey: Sequence['byEntityKey'] = {};
+    const count = entity_id.rowCount;
+
+    let i = 0;
+    while (i < count) {
+        const start = i;
+        while (i < count - 1 && entity_id.areValuesEqual(i, i + 1)) i++;
+        i++;
+
+        const id = entity_id.value(start);
+        byEntityKey[entities.getEntityIndex(id)] = { entityId: id, compId: Column.window(mon_id, start, i), num: Column.window(num, start, i)  }
+    }
+
+    return { byEntityKey };
+}

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

@@ -6,10 +6,12 @@
 
 import UUID from 'mol-util/uuid'
 import Format from './format'
+import Sequence from './properties/sequence'
 import Hierarchy from './properties/hierarchy'
-import Conformation from './properties/conformation'
+import AtomSiteConformation from './properties/atom-site-conformation'
 import Symmetry from './properties/symmetry'
 import CoarseGrained from './properties/coarse-grained'
+import { Entities } from './properties/common';
 
 import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
@@ -26,8 +28,11 @@ interface Model extends Readonly<{
 
     sourceData: Format,
 
+    entities: Entities,
+    sequence: Sequence,
+
     hierarchy: Hierarchy,
-    conformation: Conformation,
+    atomSiteConformation: AtomSiteConformation,
     symmetry: Symmetry,
     coarseGrained: CoarseGrained,
 

+ 0 - 0
src/mol-model/structure/model/properties/conformation.ts → src/mol-model/structure/model/properties/atom-site-conformation.ts


+ 40 - 2
src/mol-model/structure/model/properties/coarse-grained.ts

@@ -4,12 +4,50 @@
  * @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 {
-    // TODO
+    isDefined: boolean,
+    modelList: mmCIF['ihm_model_list'],
+    spheres: CoarseGrained.Spheres,
+    gaussians: CoarseGrained.Gaussians
 }
 
 namespace CoarseGrained {
-    export const Empty: 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;

+ 14 - 0
src/mol-model/structure/model/properties/common.ts

@@ -0,0 +1,14 @@
+/**
+ * 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'
+
+interface Entities {
+    data: mmCIF['entity'],
+    getEntityIndex(id: string): number
+}
+
+export { Entities }

+ 1 - 7
src/mol-model/structure/model/properties/hierarchy.ts

@@ -40,15 +40,10 @@ export const ChainsSchema = {
 export type ChainsSchema = typeof ChainsSchema
 export interface Chains extends Table<ChainsSchema> { }
 
-export const EntitySchema = mmCIF['entity']
-export type EntitySchema = typeof EntitySchema
-export interface Entities extends Table<EntitySchema> { }
-
 export interface Data {
     atoms: Atoms,
     residues: Residues,
-    chains: Chains,
-    entities: Entities
+    chains: Chains
 }
 
 export interface Segments {
@@ -70,7 +65,6 @@ export interface Keys {
     // also index to the Entities table.
     entityKey: Column<number>,
 
-    findEntityKey(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
 }

+ 29 - 0
src/mol-model/structure/model/properties/sequence.ts

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Column } from 'mol-data/db'
+import { Hierarchy } from './hierarchy';
+
+interface Sequence {
+    readonly byEntityKey: { [key: number]: Sequence.Entity }
+}
+
+namespace Sequence {
+    export interface Entity {
+        readonly entityId: string,
+        readonly num: Column<number>
+        // _entity_poly_seq.mon_id
+        readonly compId: Column<string>
+    }
+
+    export function fromHierarchy(hierarchy: Hierarchy): Sequence {
+        // const { label_comp_id } = hierarchy.residues;
+
+        throw 'not implemented';
+    }
+}
+
+export default Sequence

+ 19 - 13
src/mol-model/structure/model/utils/hierarchy-keys.ts

@@ -7,6 +7,7 @@
 import { Column } from 'mol-data/db'
 import { Data, Segments, Keys } from '../properties/hierarchy'
 import { Interval, Segmentation } from 'mol-data/int'
+import { Entities } from '../properties/common';
 
 function getResidueId(comp_id: string, seq_id: number, ins_code: string) {
     return `${comp_id} ${seq_id} ${ins_code}`;
@@ -26,24 +27,26 @@ function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key
     return ret;
 }
 
-function createLookUp(entity: Map<string, number>, chain: Map<number, Map<string, number>>, residue: Map<number, Map<string, number>>) {
-    const findEntityKey: Keys['findEntityKey'] = (id) => entity.has(id) ? entity.get(id)! : -1;
+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) => {
-        if (!entity.has(e)) return -1;
-        const cm = chain.get(entity.get(e)!)!;
+        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) => {
-        if (!entity.has(e)) return -1;
-        const cm = chain.get(entity.get(e)!)!;
+        let eKey = getEntKey(e);
+        if (eKey < 0) return -1;
+        const cm = chain.get(eKey)!;
         if (!cm.has(c)) return -1;
         const rm = residue.get(cm.get(c)!)!
         const id = getResidueId(name, seq, ins);
         if (!rm.has(id)) return -1;
         return rm.get(id)!;
     }
-    return { findEntityKey, findChainKey, findResidueKey };
+    return { findChainKey, findResidueKey };
 }
 
 function checkMonotonous(xs: ArrayLike<number>) {
@@ -55,10 +58,13 @@ function checkMonotonous(xs: ArrayLike<number>) {
     return true;
 }
 
-function create(data: Data, segments: Segments): Keys {
-    const { chains, residues, entities } = data;
+function missingEntity(k: string) {
+    throw new Error(`Missing entity entry for entity id '${k}'.`);
+}
+
+function create(data: Data, entities: Entities, segments: Segments): Keys {
+    const { chains, residues } = data;
 
-    const entityMap = Column.createFirstIndexMap(entities.id);
     const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
     const residueMaps = new Map<number, Map<string, number>>(), residueCounter = { index: 0 };
 
@@ -78,7 +84,8 @@ function create(data: Data, segments: Segments): Keys {
         const chainSegment = chainsIt.move();
         const cI = chainSegment.index;
 
-        const eKey = entityMap.get(label_entity_id.value(cI)) || 0;
+        let eKey = entities.getEntityIndex(label_entity_id.value(cI));
+        if (eKey < 0) missingEntity(label_entity_id.value(cI));
         const chainMap = getElementSubstructureKeyMap(chainMaps, eKey);
         const cKey = getElementKey(chainMap, label_asym_id.value(cI), chainCounter);
 
@@ -99,14 +106,13 @@ function create(data: Data, segments: Segments): Keys {
         }
     }
 
-    const { findEntityKey, findChainKey, findResidueKey } = createLookUp(entityMap, chainMaps, residueMaps);
+    const { findChainKey, findResidueKey } = createLookUp(entities, chainMaps, residueMaps);
 
     return {
         isMonotonous: isMonotonous && checkMonotonous(entityKey) && checkMonotonous(chainKey) && checkMonotonous(residueKey),
         residueKey: Column.ofIntArray(residueKey),
         chainKey: Column.ofIntArray(chainKey),
         entityKey: Column.ofIntArray(entityKey),
-        findEntityKey,
         findChainKey,
         findResidueKey
     };

+ 7 - 1
src/mol-model/structure/query/generators.ts

@@ -7,7 +7,7 @@
 import Query from './query'
 import Selection from './selection'
 import P from './properties'
-import { Structure, ElementSet, Element } from '../structure'
+import { Structure, ElementSet, Element, Unit } from '../structure'
 import { OrderedSet, Segmentation } from 'mol-data/int'
 
 export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s.elements);
@@ -79,6 +79,9 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
         for (let i = 0, _i = unitIds.length; i < _i; i++) {
             const unitId = unitIds[i];
             const unit = units[unitId];
+
+            if (unit.kind !== Unit.Kind.Atomic) continue;
+
             l.unit = unit;
             const set = ElementSet.groupAt(elements, i).elements;
 
@@ -171,6 +174,9 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
         for (let i = 0, _i = unitIds.length; i < _i; i++) {
             const unitId = unitIds[i];
             const unit = units[unitId];
+
+            if (unit.kind !== Unit.Kind.Atomic) continue;
+
             l.unit = unit;
             const set = ElementSet.groupAt(elements, i).elements;
 

+ 48 - 25
src/mol-model/structure/query/properties.ts

@@ -2,11 +2,10 @@
  * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Element, Unit } from '../structure'
-import { VdwRadius, AtomWeight, AtomNumber } from '../model/properties/atomic';
+import CoarseGrained from '../model/properties/coarse-grained';
 
 const constant = {
     true: Element.property(l => true),
@@ -18,6 +17,11 @@ function notAtomic(): never {
     throw 'Property only available for atomic models.';
 }
 
+function notCoarse(kind?: string): never {
+    if (!!kind) throw `Property only available for coarse models (${kind}).`;
+    throw `Property only available for coarse models.`;
+}
+
 const atom = {
     key: Element.property(l => l.element),
 
@@ -26,20 +30,15 @@ const atom = {
     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)),
+    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)),
 
     // Hierarchy
-    type_symbol: Element.property(l => l.unit.hierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => l.unit.hierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => l.unit.hierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => l.unit.hierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
-
-    // Derived
-    vdw: Element.property(l => VdwRadius(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
-    mass: Element.property(l => AtomWeight(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
-    number: Element.property(l => AtomNumber(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
+    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))
 }
 
 const residue = {
@@ -61,21 +60,44 @@ const chain = {
     label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.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]),
+
+    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)),
+
+    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)),
+
+    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)),
+}
+
 function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.entityKey.value(l.unit.chainIndex[l.element]); }
 
 const entity = {
     key: eK,
 
-    id: Element.property(l => l.unit.hierarchy.entities.id.value(eK(l))),
-    type: Element.property(l => l.unit.hierarchy.entities.type.value(eK(l))),
-    src_method: Element.property(l => l.unit.hierarchy.entities.src_method.value(eK(l))),
-    pdbx_description: Element.property(l => l.unit.hierarchy.entities.pdbx_description.value(eK(l))),
-    formula_weight: Element.property(l => l.unit.hierarchy.entities.formula_weight.value(eK(l))),
-    pdbx_number_of_molecules: Element.property(l => l.unit.hierarchy.entities.pdbx_number_of_molecules.value(eK(l))),
-    details: Element.property(l => l.unit.hierarchy.entities.details.value(eK(l))),
-    pdbx_mutation: Element.property(l => l.unit.hierarchy.entities.pdbx_mutation.value(eK(l))),
-    pdbx_fragment: Element.property(l => l.unit.hierarchy.entities.pdbx_fragment.value(eK(l))),
-    pdbx_ec: Element.property(l => l.unit.hierarchy.entities.pdbx_ec.value(eK(l)))
+    id: Element.property(l => l.unit.model.entities.data.id.value(eK(l))),
+    type: Element.property(l => l.unit.model.entities.data.type.value(eK(l))),
+    src_method: Element.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
+    pdbx_description: Element.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
+    formula_weight: Element.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
+    pdbx_number_of_molecules: Element.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
+    details: Element.property(l => l.unit.model.entities.data.details.value(eK(l))),
+    pdbx_mutation: Element.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
+    pdbx_fragment: Element.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
+    pdbx_ec: Element.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
 }
 
 const unit = {
@@ -89,7 +111,8 @@ const Properties = {
     residue,
     chain,
     entity,
-    unit
+    unit,
+    coarse_grained
 }
 
 type Properties = typeof Properties

+ 1 - 1
src/mol-model/structure/structure/element/properties/bonds/group-compute.ts

@@ -110,7 +110,7 @@ function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[],
 function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondComputationParameters): GroupBonds {
     const MAX_RADIUS = 3;
 
-    const { x, y, z } = unit.model.conformation;
+    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;

+ 15 - 0
src/mol-model/structure/structure/structure.ts

@@ -12,6 +12,7 @@ import Unit from './unit'
 import ElementSet from './element/set'
 import ElementGroup from './element/group'
 import Element from './element'
+import CoarseGrained from '../model/properties/coarse-grained';
 
 // A structure is a pair of "units" and an element set.
 // Each unit contains the data and transformation of its corresponding elements.
@@ -39,6 +40,20 @@ namespace Structure {
             builder.add(unit, unit.fullGroup);
         }
 
+        const cs = model.coarseGrained;
+        if (cs.isDefined) {
+            if (cs.spheres.count > 0) {
+                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.spheres.count));
+                const unit = Unit.createCoarse(model, SymmetryOperator.Default, group, CoarseGrained.ElementType.Sphere);
+                builder.add(unit, unit.fullGroup);
+            }
+            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);
+            }
+        }
+
         return builder.getStructure();
     }
 

+ 32 - 13
src/mol-model/structure/structure/unit.ts

@@ -9,6 +9,7 @@ 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';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -29,9 +30,7 @@ namespace Unit {
         // Things like inter-unit bonds or spatial lookups
         // can be be implemented efficiently as "views" of the
         // full group.
-        readonly fullGroup: ElementGroup,
-
-        readonly hierarchy: Model['hierarchy'],
+        readonly fullGroup: ElementGroup
     }
 
     // A bulding block of a structure that corresponds
@@ -47,18 +46,23 @@ namespace Unit {
         // Reference some commonly accessed things for faster access.
         readonly residueIndex: ArrayLike<number>,
         readonly chainIndex: ArrayLike<number>,
-        readonly conformation: Model['conformation']
+        readonly conformation: Model['atomSiteConformation'],
+        readonly hierarchy: Model['hierarchy']
     }
 
     // Coarse grained representations.
-    // TODO: can we use the ArrayMapping here?
     export interface Coarse extends Base  {
-        readonly kind: Unit.Kind.Coarse
+        readonly kind: Unit.Kind.Coarse,
+        readonly elementType: CoarseGrained.ElementType,
+
+        readonly siteBases: CoarseGrained.SiteBases,
+        readonly spheres: CoarseGrained.Spheres,
+        readonly gaussians: CoarseGrained.Gaussians
     }
 
-    export function createAtomic(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit {
+    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.conformation);
+        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.atomSiteConformation);
 
         return {
             model,
@@ -68,28 +72,43 @@ namespace Unit {
             residueIndex: h.residueSegments.segmentMap,
             chainIndex: h.chainSegments.segmentMap,
             hierarchy: model.hierarchy,
-            conformation: model.conformation,
+            conformation: model.atomSiteConformation,
             invariantPosition,
             position,
             x, y, z
         };
     }
 
-    export function createCoarse(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit {
-        throw 'not implemented'
+    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
+        };
     }
 
     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);
+            case Kind.Coarse: return createCoarse(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup, unit.elementType);
         }
     }
 
     export function getLookup3d(unit: Unit, group: ElementGroup) {
         if (group.__lookup3d__)  return group.__lookup3d__;
         if (Unit.isAtomic(unit)) {
-            const { x, y, z } = unit.model.conformation;
+            const { x, y, z } = unit.model.atomSiteConformation;
             group.__lookup3d__ = GridLookup3D({ x, y, z, indices: group.elements });
             return group.__lookup3d__;
         }

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

@@ -148,7 +148,7 @@ export namespace PropertyAccess {
 
         let vA = 0, cC = 0, rC = 0;
         for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unit = units[unitIds[i]];
+            const unit = units[unitIds[i]] as Unit.Atomic;
             l.unit = unit;
             const set = ElementSet.groupAt(elements, i);
 
@@ -356,11 +356,11 @@ export namespace PropertyAccess {
         // return;
 
         console.log('bs', baseline(models[0]));
-        console.log('sp', sumProperty(structures[0], l => l.unit.model.conformation.atomId.value(l.element)));
-        console.log(sumPropertySegmented(structures[0], l => l.unit.model.conformation.atomId.value(l.element)));
+        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(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.element));
-        console.log(sumPropertyAtomSetIt(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(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])));