diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 17f1b6373892df00200857c438245b3dace4b97e..b6a66fcf6aa54b9d06f42d02bd3bc5369ccfab43 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -13,8 +13,6 @@ import { Model, Structure, Element, Unit, Format, StructureProperties } from 'mo // import { Run, Progress } from 'mol-task' import { OrderedSet } from 'mol-data/int'; import { openCif, downloadCif } from './helpers'; -import { BitFlags } from 'mol-util'; -import { SecondaryStructureType } from 'mol-model/structure/model/types'; import { UnitRings } from 'mol-model/structure/structure/unit/rings'; import { Vec3 } from 'mol-math/linear-algebra'; @@ -51,7 +49,7 @@ export function residueLabel(model: Model, rI: number) { export function printSecStructure(model: Model) { console.log('\nSecondary Structure\n============='); const { residues } = model.atomicHierarchy; - const { type, key } = model.properties.secondaryStructure; + const { key, elements } = model.properties.secondaryStructure; const count = residues._rowCount; let rI = 0; @@ -60,12 +58,8 @@ export function printSecStructure(model: Model) { while (rI < count && key[start] === key[rI]) rI++; rI--; - if (BitFlags.has(type[start], SecondaryStructureType.Flag.Beta)) { - console.log(`Sheet: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`); - } else if (BitFlags.has(type[start], SecondaryStructureType.Flag.Helix)) { - console.log(`Helix: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`); - } - + const e = elements[key[start]]; + if (e.kind !== 'none') console.log(`${e.kind}: ${residueLabel(model, start)} - ${residueLabel(model, rI)}`); rI++; } } diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index 05dcf0052bb191bb92370f30a54493c38ff76a79..174ea0f8995780f61a2252a5d13dd66ea18a2f69 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/src/mol-io/writer/cif/encoder.ts @@ -21,7 +21,7 @@ import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif'; export interface Field<Key = any, Data = any> { name: string, type: Field.Type, - value(key: Key, data: Data): string | number + value(key: Key, data: Data, index: number): string | number valueKind?: (key: Key, data: Data) => Column.ValueKind, defaultFormat?: Field.Format, shouldInclude?: (data: Data) => boolean @@ -38,11 +38,11 @@ export namespace Field { export type ParamsBase<K, D> = { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, shouldInclude?: (data: D) => boolean } - export function str<K, D = any>(name: string, value: (k: K, d: D) => string, params?: ParamsBase<K, D>): Field<K, D> { + export function str<K, D = any>(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>): Field<K, D> { return { name, type: Type.Str, value, valueKind: params && params.valueKind, defaultFormat: params && params.encoder ? { encoder: params.encoder } : void 0, shouldInclude: params && params.shouldInclude }; } - export function int<K, D = any>(name: string, value: (k: K, d: D) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> { + export function int<K, D = any>(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> { return { name, type: Type.Int, @@ -53,7 +53,7 @@ export namespace Field { }; } - export function float<K, D = any>(name: string, value: (k: K, d: D) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> { + export function float<K, D = any>(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> { return { name, type: Type.Float, @@ -63,6 +63,10 @@ export namespace Field { shouldInclude: params && params.shouldInclude }; } + + export function index(name: string) { + return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) }) + } } export interface Category<Key = any, Data = any> { diff --git a/src/mol-io/writer/cif/encoder/binary.ts b/src/mol-io/writer/cif/encoder/binary.ts index 564adbfd8059a37d877d6617be08e82ae48991e6..01e07965d0295e503d134c3d2adbe19dd44667d5 100644 --- a/src/mol-io/writer/cif/encoder/binary.ts +++ b/src/mol-io/writer/cif/encoder/binary.ts @@ -141,7 +141,7 @@ function encodeField(field: Field, data: { data: any, keys: () => Iterator<any> allPresent = false; } else { mask[offset] = Column.ValueKind.Present; - array[offset] = getter(key, d); + array[offset] = getter(key, d, offset); } offset++; } diff --git a/src/mol-io/writer/cif/encoder/text.ts b/src/mol-io/writer/cif/encoder/text.ts index 55ec8ec85db359d964f72526ba0813de77fe9be5..a191627285c2297171ddaa6e3a1ff31177985b50 100644 --- a/src/mol-io/writer/cif/encoder/text.ts +++ b/src/mol-io/writer/cif/encoder/text.ts @@ -73,14 +73,14 @@ export default class TextEncoder implements Encoder<string> { } } -function writeValue(builder: StringBuilder, data: any, key: any, f: Field<any, any>, floatPrecision: number): boolean { +function writeValue(builder: StringBuilder, data: any, key: any, f: Field<any, any>, floatPrecision: number, index: number): boolean { const kind = f.valueKind; const p = kind ? kind(key, data) : Column.ValueKind.Present; if (p !== Column.ValueKind.Present) { if (p === Column.ValueKind.NotPresent) writeNotPresent(builder); else writeUnknown(builder); } else { - const val = f.value(key, data); + const val = f.value(key, data, index); const t = f.type; if (t === Field.Type.Str) { if (isMultiline(val as string)) { @@ -127,7 +127,7 @@ function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, f if (!filter.includeField(category.name, f.name)) continue; StringBuilder.writePadRight(builder, `_${category.name}.${f.name}`, width); - const multiline = writeValue(builder, data, key, f, precisions[_f]); + const multiline = writeValue(builder, data, key, f, precisions[_f], 0); if (!multiline) StringBuilder.newline(builder); } StringBuilder.write(builder, '#\n'); @@ -147,6 +147,7 @@ function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Ca writeLine(builder, `_${first.name}.${fields[i].name}`); } + let index = 0; for (let _c = 0; _c < categories.length; _c++) { const category = categories[_c]; const data = category.data; @@ -159,9 +160,10 @@ function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Ca let multiline = false; for (let _f = 0; _f < fieldCount; _f++) { - multiline = writeValue(builder, data, key, fields[_f], precisions[_f]); + multiline = writeValue(builder, data, key, fields[_f], precisions[_f], index); } if (!multiline) StringBuilder.newline(builder); + index++; } } StringBuilder.write(builder, '#\n'); diff --git a/src/mol-model/structure/export/categories/atom_site.ts b/src/mol-model/structure/export/categories/atom_site.ts index 7ee77164560dbb31d8faf949370b1b250986595d..c5b1fab70a58a5784148c82a2702321356edc533 100644 --- a/src/mol-model/structure/export/categories/atom_site.ts +++ b/src/mol-model/structure/export/categories/atom_site.ts @@ -14,7 +14,7 @@ import E = CifWriter.Encodings const atom_site_fields: CifField<Element.Location>[] = [ CifField.str('group_PDB', P.residue.group_PDB), - CifField.int('id', P.atom.id, { encoder: E.deltaRLE }), + CifField.index('id'), CifField.str('type_symbol', P.atom.type_symbol as any), CifField.str('label_atom_id', P.atom.label_atom_id), CifField.str('label_alt_id', P.atom.label_alt_id), diff --git a/src/mol-model/structure/export/categories/secondary-structure.ts b/src/mol-model/structure/export/categories/secondary-structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..35936e1652cc3da117ebabbe1a95d0b855f06662 --- /dev/null +++ b/src/mol-model/structure/export/categories/secondary-structure.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Segmentation } from 'mol-data/int'; +import { CifWriter } from 'mol-io/writer/cif'; +import { SecondaryStructure } from '../../model/properties/seconday-structure'; +import { Element, Unit, StructureProperties as P } from '../../structure'; +import { CifExportContext } from '../mmcif'; + +import CifField = CifWriter.Field +import CifCategory = CifWriter.Category +import { Column } from 'mol-data/db'; + +export function _struct_conf(ctx: CifExportContext): CifCategory { + const elements = findElements(ctx, 'helix'); + return { + data: elements, + name: 'struct_conf', + fields: struct_conf_fields, + rowCount: elements.length + }; +} + +export function _struct_sheet_range(ctx: CifExportContext): CifCategory { + const elements = findElements(ctx, 'sheet'); + return { + data: elements, + name: 'struct_sheet_range', + fields: struct_sheet_range_fields, + rowCount: elements.length + }; +} + +const struct_conf_fields: CifField[] = [ + CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data) => data[i].element.type_id), + CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data, idx) => `${data[i].element.type_id}${idx + 1}`), + ...residueIdFields('beg_', e => e.start), + ...residueIdFields('end_', e => e.end), + CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].element.helix_class), + CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('details', (i, data) => data[i].element.details || '', { + valueKind: (i, d) => !!d[i].element.details ? Column.ValueKind.Present : Column.ValueKind.Unknown + }), + CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].length) +]; + +const struct_sheet_range_fields: CifField[] = [ + CifField.index('id'), + CifField.str<number, SSElement<SecondaryStructure.Sheet>[]>('sheet_id', (i, data) => data[i].element.sheet_id), + ...residueIdFields('beg_', e => e.start), + ...residueIdFields('end_', e => e.end), + CifField.str('symmetry', (i, data) => '', { valueKind: (i, d) => Column.ValueKind.Unknown }) +]; + +function residueIdFields(prefix: string, loc: (e: SSElement<any>) => Element.Location): CifField<number, SSElement<SecondaryStructure.Helix>[]>[] { + return [ + CifField.str(`${prefix}label_comp_id`, (i, d) => P.residue.label_comp_id(loc(d[i]))), + CifField.int(`${prefix}label_seq_id`, (i, d) => P.residue.label_seq_id(loc(d[i]))), + CifField.str(`pdbx_${prefix}PDB_ins_code`, (i, d) => P.residue.pdbx_PDB_ins_code(loc(d[i]))), + CifField.str(`${prefix}label_asym_id`, (i, d) => P.chain.label_asym_id(loc(d[i]))), + CifField.str(`${prefix}_entity_id`, (i, d) => P.chain.label_entity_id(loc(d[i]))), + CifField.str(`${prefix}auth_comp_id`, (i, d) => P.residue.auth_comp_id(loc(d[i]))), + CifField.int(`${prefix}auth_seq_id`, (i, d) => P.residue.auth_seq_id(loc(d[i]))), + CifField.str(`${prefix}auth_asym_id`, (i, d) => P.chain.auth_asym_id(loc(d[i]))) + ]; +} + +interface SSElement<T extends SecondaryStructure.Element> { + start: Element.Location, + end: Element.Location, + length: number, + element: T +} + +function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) { + const { key, elements } = ctx.model.properties.secondaryStructure; + + const ssElements: SSElement<any>[] = []; + + for (const unit of ctx.structure.units) { + // currently can only support this for "identity" operators. + if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue; + + const segs = unit.model.atomicHierarchy.residueSegments; + const residues = Segmentation.transientSegments(segs, unit.elements); + + let current: Segmentation.Segment<Element>, move = true; + while (residues.hasNext) { + if (move) current = residues.move(); + + const start = current!.index; + const startIdx = key[start]; + const element = elements[startIdx]; + if (element.kind !== kind) { + move = true; + continue; + } + + let prev = start; + while (residues.hasNext) { + prev = current!.index; + current = residues.move(); + if (startIdx !== key[current.index]) { + move = false; + ssElements[ssElements.length] = { + start: Element.Location(unit, segs.segments[start]), + end: Element.Location(unit, segs.segments[prev]), + length: prev - start + 1, + element + } + break; + } + } + } + } + + return ssElements as SSElement<T>[]; +} \ No newline at end of file diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index eca4f961ee652d1a817f7adce4359838e165b3f5..c783b552e041369ec50d4466e299357d476119c1 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -17,6 +17,7 @@ export interface CifExportContext { } import CifCategory = CifWriter.Category +import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure'; function copy_mmCif_category(name: keyof mmCIF_Schema) { return ({ model }: CifExportContext) => { @@ -33,12 +34,30 @@ function _entity({ model, structure }: CifExportContext): CifCategory { } const Categories = [ + // Basics copy_mmCif_category('entry'), copy_mmCif_category('exptl'), + _entity, + + // Symmetry copy_mmCif_category('cell'), copy_mmCif_category('symmetry'), + + // Assemblies + copy_mmCif_category('pdbx_struct_assembly'), + copy_mmCif_category('pdbx_struct_assembly_gen'), + copy_mmCif_category('pdbx_struct_oper_list'), + + // Secondary structure + _struct_conf, + _struct_sheet_range, + + // Misc + // TODO: filter for actual present residues? copy_mmCif_category('chem_comp'), - _entity, + copy_mmCif_category('atom_sites'), + + // Atoms _atom_site ]; diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index a3f45dbf1b122e4c4fc0b4493001613b974f7547..17b7ee9cf9f4df0ad0ab90bc398af82163679569 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -22,11 +22,12 @@ import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './mmcif/ihm'; import { getSecondaryStructureMmCif } from './mmcif/secondary-structure'; import { getSequence } from './mmcif/sequence'; import { sortAtomSite } from './mmcif/sort'; - -import mmCIF_Format = Format.mmCIF +import { StructConn } from './mmcif/bonds/struct_conn'; import { ChemicalComponent } from '../properties/chemical-component'; import { ComponentType, getMoleculeType } from '../types'; +import mmCIF_Format = Format.mmCIF + type AtomSite = mmCIF_Database['atom_site'] function getSymmetry(format: mmCIF_Format): ModelSymmetry { @@ -192,6 +193,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData): Model { function attachProps(model: Model) { ComponentBond.attachFromMmCif(model); + StructConn.attachFromMmCif(model); } function findModelEnd(num: Column<number>, startIndex: number) { diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts index 04d74a0a7910dc3643e785f912e1f2eba400f829..b89b473df9e6dfccb1d0732099290c60a1c4382d 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts +++ b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts @@ -24,11 +24,10 @@ export namespace ComponentBond { cifExport: { categoryNames: ['chem_comp_bond'], categoryProvider(ctx) { - const comp_names = getUniqueResidueNames(ctx.structure); const chem_comp_bond = getChemCompBond(ctx.model); - if (!chem_comp_bond) return []; + const comp_names = getUniqueResidueNames(ctx.structure); const { comp_id, _rowCount } = chem_comp_bond; const indices: number[] = []; for (let i = 0; i < _rowCount; i++) { @@ -95,7 +94,7 @@ export namespace ComponentBond { if (!model.customProperties.has(Descriptor)) return void 0; const chem_comp_bond = getChemCompBond(model); - let compBond = new ComponentBondImpl(); + const compBond = new ComponentBondImpl(); const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond; diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts b/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts index d0c0e5f07e0ab70bc4db546a0dcc68395a06b5d4..f2ea4703a18f1ef90982750a70c37a1036e963ab 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts +++ b/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts @@ -6,17 +6,68 @@ */ import Model from '../../../model' -import { Element } from '../../../../structure' +import { Element, Structure } from '../../../../structure' import { LinkType } from '../../../types' import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util' import { Column } from 'mol-data/db' +import { ModelPropertyDescriptor } from '../../../properties/custom'; +import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; +import { SortedArray } from 'mol-data/int'; +import { CifWriter } from 'mol-io/writer/cif' export interface StructConn { - getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> - getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> + getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>, + getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>, + readonly entries: ReadonlyArray<StructConn.Entry> } export namespace StructConn { + export const Descriptor: ModelPropertyDescriptor = { + isStatic: true, + name: 'struct_conn', + cifExport: { + categoryNames: ['struct_conn'], + categoryProvider(ctx) { + const struct_conn = getStructConn(ctx.model); + if (!struct_conn) return []; + + const strConn = get(ctx.model); + if (!strConn || strConn.entries.length === 0) return []; + + const foundAtoms = new Set<Element>(); + const indices: number[] = []; + for (const entry of strConn.entries) { + const { partners } = entry; + let hasAll = true; + for (let i = 0, _i = partners.length; i < _i; i++) { + const atom = partners[i].atomIndex; + if (foundAtoms.has(atom)) continue; + if (hasAtom(ctx.structure, atom)) { + foundAtoms.add(atom); + } else { + hasAll = false; + break; + } + } + if (hasAll) { + indices[indices.length] = entry.rowIndex; + } + } + + return [ + () => CifWriter.Category.ofTable('struct_conn', struct_conn, indices) + ]; + } + } + } + + function hasAtom({ units }: Structure, element: Element) { + for (let i = 0, _i = units.length; i < _i; i++) { + if (SortedArray.indexOf(units[i].elements, element) >= 0) return true; + } + return false; + } + function _resKey(rA: number, rB: number) { if (rA < rB) return `${rA}-${rB}`; return `${rB}-${rA}`; @@ -77,6 +128,7 @@ export namespace StructConn { } export interface Entry { + rowIndex: number, distance: number, order: number, flags: number, @@ -95,19 +147,33 @@ export namespace StructConn { | 'modres' | 'saltbr' + export function attachFromMmCif(model: Model): boolean { + if (model.customProperties.has(Descriptor)) return true; + if (model.sourceData.kind !== 'mmCIF') return false; + const { struct_conn } = model.sourceData.data; + if (struct_conn._rowCount === 0) return false; + model.customProperties.add(Descriptor); + model._staticPropertyData.__StructConnData__ = struct_conn; + return true; + } + + function getStructConn(model: Model) { + return model._staticPropertyData.__StructConnData__ as mmCIF_Database['struct_conn']; + } + export const PropName = '__StructConn__'; - export function fromModel(model: Model): StructConn | undefined { + export function get(model: Model): StructConn | undefined { if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]; + if (!model.customProperties.has(Descriptor)) return void 0; - if (model.sourceData.kind !== 'mmCIF') return; - const { struct_conn } = model.sourceData.data; - if (!struct_conn._rowCount) return void 0; + const struct_conn = getStructConn(model); const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn; const p1 = { label_asym_id: struct_conn.ptnr1_label_asym_id, label_comp_id: struct_conn.ptnr1_label_comp_id, label_seq_id: struct_conn.ptnr1_label_seq_id, + auth_seq_id: struct_conn.ptnr1_auth_seq_id, label_atom_id: struct_conn.ptnr1_label_atom_id, label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id, ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code, @@ -117,6 +183,7 @@ export namespace StructConn { label_asym_id: struct_conn.ptnr2_label_asym_id, label_comp_id: struct_conn.ptnr2_label_comp_id, label_seq_id: struct_conn.ptnr2_label_seq_id, + auth_seq_id: struct_conn.ptnr2_auth_seq_id, label_atom_id: struct_conn.ptnr2_label_atom_id, label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id, ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code, @@ -128,9 +195,9 @@ export namespace StructConn { const asymId = ps.label_asym_id.value(row) const residueIndex = model.atomicHierarchy.findResidueKey( findEntityIdByAsymId(model, asymId), - ps.label_comp_id.value(row), asymId, - ps.label_seq_id.value(row), + ps.label_comp_id.value(row), + ps.auth_seq_id.value(row), ps.ins_code.value(row) ); if (residueIndex < 0) return void 0; @@ -182,7 +249,7 @@ export namespace StructConn { case 'saltbr': flags = LinkType.Flag.Ion; break; } - entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners }); + entries.push({ rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partners }); } const ret = new StructConnImpl(entries); diff --git a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts index ed07db38bf79d75bc18c3382989e5a2ca14e8796..0c045f6052e08bcd1b940eb3b4e940e51fdfdae7 100644 --- a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts +++ b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts @@ -13,13 +13,15 @@ import { Column } from 'mol-data/db'; export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure { const map: SecondaryStructureMap = new Map(); - addHelices(data.struct_conf, map); + const elements: SecondaryStructure.Element[] = [{ kind: 'none' }]; + addHelices(data.struct_conf, map, elements); // must add Helices 1st because of 'key' value assignment. - addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount); + addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount, elements); const secStruct: SecondaryStructureData = { type: new Int32Array(hierarchy.residues._rowCount) as any, - key: new Int32Array(hierarchy.residues._rowCount) as any + key: new Int32Array(hierarchy.residues._rowCount) as any, + elements }; if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct); @@ -35,31 +37,40 @@ type SecondaryStructureEntry = { key: number } type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry>> -type SecondaryStructureData = { type: SecondaryStructureType[], key: number[] } +type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] } -function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) { +function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) { if (!cat._rowCount) return; const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat; const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat; - const { pdbx_PDB_helix_class, conf_type_id } = cat; + const { pdbx_PDB_helix_class, conf_type_id, details } = cat; for (let i = 0, _i = cat._rowCount; i < _i; i++) { - const type = pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present + const type = SecondaryStructureType.create(pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present ? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)] : conf_type_id.valueKind(i) === Column.ValueKind.Present ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)] - : SecondaryStructureType.Flag.NA - + : SecondaryStructureType.Flag.NA); + + const element: SecondaryStructure.Helix = { + kind: 'helix', + flags: type, + type_id: conf_type_id.valueKind(i) === Column.ValueKind.Present ? conf_type_id.value(i) : 'HELIX_P', + helix_class: pdbx_PDB_helix_class.value(i), + details: details.valueKind(i) === Column.ValueKind.Present ? details.value(i) : void 0 + }; const entry: SecondaryStructureEntry = { startSeqNumber: beg_label_seq_id.value(i), startInsCode: pdbx_beg_PDB_ins_code.value(i), endSeqNumber: end_label_seq_id.value(i), endInsCode: pdbx_end_PDB_ins_code.value(i), - type: SecondaryStructureType.create(type), - key: i + 1 + type, + key: elements.length }; + elements[elements.length] = element; + const asymId = beg_label_asym_id.value(i)!; if (map.has(asymId)) { map.get(asymId)!.set(entry.startSeqNumber, entry); @@ -69,7 +80,7 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) { } } -function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number) { +function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) { if (!cat._rowCount) return; const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat; @@ -88,15 +99,25 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheet_id_key.set(id, key); } + const type = SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet); + const element: SecondaryStructure.Sheet = { + kind: 'sheet', + flags: type, + sheet_id: id, + symmetry: void 0 + } const entry: SecondaryStructureEntry = { startSeqNumber: beg_label_seq_id.value(i), startInsCode: pdbx_beg_PDB_ins_code.value(i), endSeqNumber: end_label_seq_id.value(i), endInsCode: pdbx_end_PDB_ins_code.value(i), - type: SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet), - key + type, + key: elements.length }; + elements[elements.length] = element; + + const asymId = beg_label_asym_id.value(i)!; if (map.has(asymId)) { map.get(asymId)!.set(entry.startSeqNumber, entry); @@ -110,12 +131,11 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) { const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues; - const { endSeqNumber, endInsCode, type, key } = entry; + const { endSeqNumber, endInsCode, key, type } = entry; let rI = resStart; while (rI < resEnd) { const seqNumber = label_seq_id.value(rI); - data.type[rI] = type; data.key[rI] = key; diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts index 2bccf1a97fb9209d8250ebc4b726447bb29214d3..672acf2efc9049c90a361e289088ce1240100c8b 100644 --- a/src/mol-model/structure/model/formats/mmcif/util.ts +++ b/src/mol-model/structure/model/formats/mmcif/util.ts @@ -17,10 +17,9 @@ export function findEntityIdByAsymId(model: Model, asymId: string) { } export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): Element { - const { segmentMap, segments } = model.atomicHierarchy.residueSegments - const idx = segmentMap[residueIndex] + const { segments } = model.atomicHierarchy.residueSegments; const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms; - for (let i = segments[idx], n = segments[idx + 1]; i <= n; ++i) { + for (let i = segments[residueIndex], n = segments[residueIndex + 1]; i < n; ++i) { if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as Element; } return -1 as Element; diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index d09cc88b2cf9fb28c29589b1e7db52eff24249a5..12c9de4c1ed01dfbaaa055a7d2e7756ced2ed743 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -48,7 +48,9 @@ export interface AtomicData { } export interface AtomicSegments { + /** Maps residueIndex to a range of atoms [segments[rI], segments[rI + 1]) */ residueSegments: Segmentation<Element>, + /** Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]) */ chainSegments: Segmentation<Element>, /** * bonded/connected stretches of polymer chains, i.e. a chain will be diff --git a/src/mol-model/structure/model/properties/seconday-structure.ts b/src/mol-model/structure/model/properties/seconday-structure.ts index dfde631a30ecb78ca0e854b5d9868b7861f953a0..49618f0071fc1974657ffd2f01cd1ffaf5c18d31 100644 --- a/src/mol-model/structure/model/properties/seconday-structure.ts +++ b/src/mol-model/structure/model/properties/seconday-structure.ts @@ -8,10 +8,35 @@ import { SecondaryStructureType } from '../types'; /** Secondary structure "indexed" by residues. */ interface SecondaryStructure { - // assign flags to each residue readonly type: ArrayLike<SecondaryStructureType>, - /** unique value for each "element". This is because single sheet is speficied by multiple records. */ - readonly key: ArrayLike<number> + + /** index into the elements array */ + readonly key: ArrayLike<number>, + /** indexed by key */ + readonly elements: ReadonlyArray<SecondaryStructure.Element> +} + +namespace SecondaryStructure { + export type Element = None | Helix | Sheet + + export interface None { + kind: 'none' + } + + export interface Helix { + kind: 'helix', + flags: SecondaryStructureType, + type_id: string, // TODO: use aliased type? + helix_class: string, + details?: string + } + + export interface Sheet { + kind: 'sheet', + flags: SecondaryStructureType, + sheet_id: string, + symmetry?: string + } } export { SecondaryStructure } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index 53b6437e2da7db7c340fa28d662ae19ea78bbaf9..d96698455f54d48f84afac353ce4a7339c1aee8d 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -49,7 +49,7 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu const { type_symbol: type_symbolA, label_alt_id: label_alt_idA } = unitA.model.atomicHierarchy.atoms; const { type_symbol: type_symbolB, label_alt_id: label_alt_idB } = unitB.model.atomicHierarchy.atoms; const { lookup3d } = unitB; - const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unitA.model) : void 0; + const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0; // the lookup queries need to happen in the "unitB space". // that means imageA = inverseOperB(operA(aI)) diff --git a/src/mol-model/structure/structure/unit/links/intra-compute.ts b/src/mol-model/structure/structure/unit/links/intra-compute.ts index c17d8153972e6254db102ac5e02db46191671bda..5ba8eb95b351a01f472f10132a16f190af4ece99 100644 --- a/src/mol-model/structure/structure/unit/links/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/links/intra-compute.ts @@ -35,7 +35,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In const { label_comp_id } = unit.model.atomicHierarchy.residues; const query3d = unit.lookup3d; - const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unit.model) : void 0; + const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.get(unit.model) : void 0; const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.get(unit.model) : void 0; const atomA: number[] = [];