diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts index ae9b77cecb234164a676b62a677f291548e627ca..222d71d2e94642469c4183dd2273b42768650004 100644 --- a/src/mol-data/db/table.ts +++ b/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; diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts index 3f376d1fe0ea4acc400dfc413629b6d54b8420b6..d463c30d70af897445b2293aca0726a14d53119a 100644 --- a/src/mol-model/structure/model/format.ts +++ b/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 } } diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts new file mode 100644 index 0000000000000000000000000000000000000000..9958d2a72ae0f656effe86ddc7bcd59eb86f588a --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 0a24bb472615be0297e7388c35057132dc4d7ed4..7c9f435aa36331da1b4154d917604bf972b498c5 100644 --- a/src/mol-model/structure/model/model.ts +++ b/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); } } diff --git a/src/mol-model/structure/model/utils/guess-element.ts b/src/mol-model/structure/model/utils/guess-element.ts new file mode 100644 index 0000000000000000000000000000000000000000..05658249f5aed93f771b66cae471b9a74be22955 --- /dev/null +++ b/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 '' +}