Explorar o código

add gro format import support to mol-model

Alexander Rose %!s(int64=7) %!d(string=hai) anos
pai
achega
4c74909619

+ 12 - 0
src/mol-data/db/table.ts

@@ -46,6 +46,18 @@ namespace Table {
         return { _rowCount, _columns, _schema: schema, ...(columns as any) };
     }
 
+    export function ofUndefinedColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rowCount: number): R {
+        const ret = Object.create(null);
+        const columns = Object.keys(schema);
+        ret._rowCount = rowCount;
+        ret._columns = columns;
+        ret._schema = schema;
+        for (const k of columns) {
+            ret[k] = Column.Undefined(rowCount, schema[k])
+        }
+        return ret;
+    }
+
     export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Row<S>>): R {
         const ret = Object.create(null);
         const rowCount = rows.length;

+ 3 - 0
src/mol-model/structure/model/format.ts

@@ -4,12 +4,15 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+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.mmCIF
 
 namespace Format {
+    export interface gro { kind: 'gro', data: GroFile }
     export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database }
 }
 

+ 135 - 0
src/mol-model/structure/model/formats/gro.ts

@@ -0,0 +1,135 @@
+/**
+ * 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 * as Hierarchy from '../properties/hierarchy'
+import Conformation from '../properties/conformation'
+import findHierarchyKeys from '../utils/hierarchy-keys'
+import { guessElement } from '../utils/guess-element'
+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): Hierarchy.Data {
+    console.log(atomsData.atomName)
+    const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
+        type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
+        label_atom_id: atomsData.atomName,
+        auth_atom_id: atomsData.atomName,
+        label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str),
+        pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
+    });
+
+    const residues = Table.view(Table.ofColumns(Hierarchy.ResiduesSchema, {
+        group_PDB: Column.Undefined(atomsData.count, Column.Schema.str),
+        label_comp_id: atomsData.residueName,
+        auth_comp_id: atomsData.residueName,
+        label_seq_id: atomsData.residueNumber,
+        auth_seq_id: atomsData.residueNumber,
+        pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str)
+    }), Hierarchy.ResiduesSchema, offsets.residues);
+    // Optimize the numeric columns
+    Table.columnToArray(residues, 'label_seq_id', Int32Array);
+    Table.columnToArray(residues, 'auth_seq_id', Int32Array);
+
+    // const chains = Table.ofColumns(Hierarchy.ChainsSchema, {
+    //     label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+    //     auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+    //     label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
+    // });
+
+    const chains = Table.ofUndefinedColumns(Hierarchy.ChainsSchema, 0);
+    const entities = Table.ofUndefinedColumns(Hierarchy.EntitySchema, 0);
+
+    return { atoms, residues, chains, entities };
+}
+
+function getConformation(atoms: Atoms): Conformation {
+    return {
+        id: UUID.create(),
+        atomId: atoms.atomNumber,
+        occupancy: Column.Undefined(atoms.count, Column.Schema.int),
+        B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float),
+        x: Column.mapToArray(atoms.x, x => x * 10, Float32Array),
+        y: Column.mapToArray(atoms.y, y => y * 10, Float32Array),
+        z: Column.mapToArray(atoms.z, z => z * 10, Float32Array)
+    }
+}
+
+function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) {
+    // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
+    return Table.areEqual(a.residues as Table<Hierarchy.ResiduesSchema>, b.residues as Table<Hierarchy.ResiduesSchema>)
+        && Table.areEqual(a.atoms as Table<Hierarchy.AtomsSchema>, b.atoms as Table<Hierarchy.AtomsSchema>)
+}
+
+function createModel(format: gro_Format, modelNum: number, previous?: Model): Model {
+    const structure = format.data.structures[modelNum];
+    const bounds = Interval.ofBounds(0, structure.atoms.count);
+
+    const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds);
+    const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets);
+
+    if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
+        return {
+            ...previous,
+            conformation: getConformation(structure.atoms)
+        };
+    }
+
+    const hierarchySegments: Hierarchy.Segments = {
+        residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
+        chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
+    }
+    const hierarchyKeys = findHierarchyKeys(hierarchyData, hierarchySegments);
+    return {
+        id: UUID.create(),
+        sourceData: format,
+        modelNum,
+        hierarchy: { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments },
+        conformation: getConformation(structure.atoms),
+        symmetry: { assemblies: [] },
+        atomCount: structure.atoms.count
+    };
+}
+
+function buildModels(format: gro_Format): ReadonlyArray<Model> {
+    const models: Model[] = [];
+
+    format.data.structures.forEach((_, i) => {
+        const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0);
+        models.push(model);
+    });
+    return models;
+}
+
+export default buildModels;

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

@@ -9,6 +9,8 @@ import Format from './format'
 import Hierarchy from './properties/hierarchy'
 import Conformation from './properties/conformation'
 import Symmetry from './properties/symmetry'
+
+import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
 
 
@@ -34,6 +36,7 @@ interface Model extends Readonly<{
 namespace Model {
     export function create(format: Format) {
         switch (format.kind) {
+            case 'gro': return from_gro(format);
             case 'mmCIF': return from_mmCIF(format);
         }
     }

+ 33 - 0
src/mol-model/structure/model/utils/guess-element.ts

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+const elm1 = [ 'H', 'C', 'O', 'N', 'S', 'P' ]
+const elm2 = [ 'NA', 'CL', 'FE' ]
+
+function charAtIsNumber(str: string, index: number) {
+    const code = str.charCodeAt(index)
+    return code >= 48 && code <= 57
+}
+
+export function guessElement (str: string) {
+    let at = str.trim().toUpperCase()
+
+    if (charAtIsNumber(at, 0)) at = at.substr(1)
+    // parse again to check for a second integer
+    if (charAtIsNumber(at, 0)) at = at.substr(1)
+    const n = at.length
+
+    if (n === 0) return ''
+    if (n === 1) return at
+    if (n === 2) {
+        if (elm2.indexOf(at) !== -1) return at
+        if (elm1.indexOf(at[0]) !== -1) return at[0]
+    }
+    if (n >= 3) {
+        if (elm1.indexOf(at[0]) !== -1) return at[0]
+    }
+    return ''
+}