diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index b1873f5ca463e0954ddcca443aef752ceb8a32d0..c04627418a078014a7f89f061b8f0d5bbde55fef 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -119,7 +119,7 @@ export function printSequence(model: Model) { export function printModRes(model: Model) { console.log('\nModified Residues\n============='); - const map = model.properties.modifiedResidueNameMap; + const map = model.properties.modifiedResidues.parentId; const { label_comp_id, _rowCount } = model.atomicHierarchy.residues; for (let i = 0; i < _rowCount; i++) { const comp_id = label_comp_id.value(i); diff --git a/src/mol-data/int/impl/segmentation.ts b/src/mol-data/int/impl/segmentation.ts index c43d04ba368c1bbadac09414221c27bf1d733e6b..0b1e6eba5f8235b4d60cd54654d3f6ba8fb92312 100644 --- a/src/mol-data/int/impl/segmentation.ts +++ b/src/mol-data/int/impl/segmentation.ts @@ -50,18 +50,18 @@ export function projectValue({ offsets }: Segmentation, set: OrderedSet, value: return OrderedSet.findRange(set, OrderedSet.getAt(offsets, idx), OrderedSet.getAt(offsets, idx + 1) - 1); } -export class SegmentIterator<T extends number = number> implements Iterator<Segs.Segment<T>> { +export class SegmentIterator<I extends number = number> implements Iterator<Segs.Segment<I>> { private segmentMin = 0; private segmentMax = 0; private setRange = Interval.Empty; - private value: Segs.Segment<T> = { index: 0, start: 0 as T, end: 0 as T }; + private value: Segs.Segment<I> = { index: 0 as I, start: 0, end: 0 }; hasNext: boolean = false; move() { while (this.hasNext) { if (this.updateValue()) { - this.value.index = this.segmentMin++; + this.value.index = this.segmentMin++ as I; this.hasNext = this.segmentMax >= this.segmentMin && Interval.size(this.setRange) > 0; break; } else { @@ -75,8 +75,8 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs const segmentEnd = this.segments[this.segmentMin + 1]; // TODO: add optimized version for interval and array? const setEnd = OrderedSet.findPredecessorIndexInInterval(this.set, segmentEnd, this.setRange); - this.value.start = Interval.start(this.setRange) as T; - this.value.end = setEnd as T; + this.value.start = Interval.start(this.setRange); + this.value.end = setEnd; this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange)); return setEnd > this.value.start; } @@ -94,7 +94,7 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs this.hasNext = this.segmentMax >= this.segmentMin; } - setSegment(segment: Segs.Segment<T>) { + setSegment(segment: Segs.Segment<number>) { this.setRange = Interval.ofBounds(segment.start, segment.end); this.updateSegmentRange(); } diff --git a/src/mol-data/int/segmentation.ts b/src/mol-data/int/segmentation.ts index 2afdd6a55089cf18f2316d5559a7eff80e053654..b472ea3d79dad03cc8f403191291ebb673e865f5 100644 --- a/src/mol-data/int/segmentation.ts +++ b/src/mol-data/int/segmentation.ts @@ -9,7 +9,7 @@ import OrderedSet from './ordered-set' import * as Impl from './impl/segmentation' namespace Segmentation { - export interface Segment<T extends number = number, I extends number = number> { index: number, start: T, end: T } + export interface Segment<I extends number = number> { index: I, start: number, end: number } export const create: <T extends number = number, I extends number = number>(segs: ArrayLike<T>) => Segmentation<T, I> = Impl.create as any; export const ofOffsets: <T extends number = number, I extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T, I> = Impl.ofOffsets as any; @@ -19,7 +19,7 @@ namespace Segmentation { export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any; // Segment iterator that mutates a single segment object to mark all the segments. - export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment<T>) => Impl.SegmentIterator<T> = Impl.segments as any; + export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any; } interface Segmentation<T extends number = number, I extends number = number> { diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts index 4c80e946627cb549211a8514c1d1b9e68c908b25..bfb90b50ec4839fb25c90c9b319fbc3c695691bc 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, StructureElement, StructureProperties, ElementIndex } from 'mol-model/structure'; +import { Unit, StructureElement, StructureProperties } from 'mol-model/structure'; import { Segmentation } from 'mol-data/int'; import { MoleculeType } from 'mol-model/structure/model/types'; import Iterator from 'mol-data/iterator'; @@ -50,7 +50,7 @@ function getTraceName(l: StructureElement) { return traceName } -function setTraceElement(l: StructureElement, residueSegment: Segmentation.Segment<ElementIndex>) { +function setTraceElement(l: StructureElement, residueSegment: Segmentation.Segment) { const elements = l.unit.elements l.element = elements[residueSegment.start] const traceName = getTraceName(l) @@ -97,9 +97,9 @@ const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextR export class AtomicPolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { private value: PolymerBackbonePair - private polymerIt: SegmentIterator<ElementIndex> - private residueIt: SegmentIterator<ElementIndex> - private polymerSegment: Segmentation.Segment<ElementIndex> + private polymerIt: SegmentIterator + private residueIt: SegmentIterator + private polymerSegment: Segmentation.Segment private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer private pos: SymmetryOperator.CoordinateMapper @@ -161,8 +161,8 @@ const enum CoarsePolymerBackboneIteratorState { nextPolymer, firstElement, nextE export class CoarsePolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { private value: PolymerBackbonePair - private polymerIt: SegmentIterator<ElementIndex> - private polymerSegment: Segmentation.Segment<ElementIndex> + private polymerIt: SegmentIterator + private polymerSegment: Segmentation.Segment private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer private pos: SymmetryOperator.CoordinateMapper private elementIndex: number @@ -268,8 +268,8 @@ function createPolymerTraceElement (unit: Unit) { export class AtomicPolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> { private value: PolymerTraceElement - private polymerIt: SegmentIterator<ElementIndex> - private residueIt: SegmentIterator<ElementIndex> + private polymerIt: SegmentIterator + private residueIt: SegmentIterator // private polymerSegment: Segmentation.Segment<Element> // private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer // private pos: SymmetryOperator.CoordinateMapper diff --git a/src/mol-model/structure/export/categories/modified-residues.ts b/src/mol-model/structure/export/categories/modified-residues.ts new file mode 100644 index 0000000000000000000000000000000000000000..58cdc137fb9daf474b9089c67b3ba9cacb69f7d2 --- /dev/null +++ b/src/mol-model/structure/export/categories/modified-residues.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2017-2018 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 { Segmentation } from 'mol-data/int'; +import { CifWriter } from 'mol-io/writer/cif'; +import { StructureElement, StructureProperties as P, Unit } from '../../../structure'; +import { CifExportContext } from '../mmcif'; + +import CifField = CifWriter.Field +import CifCategory = CifWriter.Category + +export function _pdbx_struct_mod_residue(ctx: CifExportContext): CifCategory { + const residues = getModifiedResidues(ctx); + return { + data: residues, + name: 'pdbx_struct_mod_residue', + fields: pdbx_struct_mod_residue_fields, + rowCount: residues.length + }; +} + +const pdbx_struct_mod_residue_fields: CifField<number, StructureElement[]>[] = [ + CifField.index('id'), + CifField.str(`label_comp_id`, (i, xs) => P.residue.label_comp_id(xs[i])), + CifField.int(`label_seq_id`, (i, xs) => P.residue.label_seq_id(xs[i])), + CifField.str(`pdbx_PDB_ins_code`, (i, xs) => P.residue.pdbx_PDB_ins_code(xs[i])), + CifField.str(`label_asym_id`, (i, xs) => P.chain.label_asym_id(xs[i])), + CifField.str(`label_entity_id`, (i, xs) => P.chain.label_entity_id(xs[i])), + CifField.str(`auth_comp_id`, (i, xs) => P.residue.auth_comp_id(xs[i])), + CifField.int(`auth_seq_id`, (i, xs) => P.residue.auth_seq_id(xs[i])), + CifField.str(`auth_asym_id`, (i, xs) => P.chain.auth_asym_id(xs[i])), + CifField.str<number, StructureElement[]>('parent_comp_id', (i, xs) => xs[i].unit.model.properties.modifiedResidues.parentId.get(P.residue.label_comp_id(xs[i]))!), + CifField.str('details', (i, xs) => xs[i].unit.model.properties.modifiedResidues.details.get(P.residue.label_comp_id(xs[i]))!) +]; + +function getModifiedResidues({ model, structure }: CifExportContext): StructureElement[] { + const map = model.properties.modifiedResidues.parentId; + if (!map.size) return []; + + const ret = []; + const prop = P.residue.label_comp_id; + const loc = StructureElement.create(); + for (const unit of structure.units) { + if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue; + const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); + loc.unit = unit; + while (residues.hasNext) { + const seg = residues.move(); + loc.element = unit.elements[seg.start]; + const name = prop(loc); + if (map.has(name)) { + ret[ret.length] = StructureElement.create(loc.unit, loc.element); + } + } + } + return ret; +} \ No newline at end of file diff --git a/src/mol-model/structure/export/categories/secondary-structure.ts b/src/mol-model/structure/export/categories/secondary-structure.ts index dbc7b29ab534cfc3b432c93f2a2d7b7e2acab5d2..aa794fd078d7650ca5180e19076e136d24c85c56 100644 --- a/src/mol-model/structure/export/categories/secondary-structure.ts +++ b/src/mol-model/structure/export/categories/secondary-structure.ts @@ -9,11 +9,9 @@ import { CifWriter } from 'mol-io/writer/cif'; import { SecondaryStructure } from '../../model/properties/seconday-structure'; import { StructureElement, Unit, StructureProperties as P } from '../../structure'; import { CifExportContext } from '../mmcif'; - import CifField = CifWriter.Field import CifCategory = CifWriter.Category import { Column } from 'mol-data/db'; -import { ElementIndex } from '../../model'; export function _struct_conf(ctx: CifExportContext): CifCategory { const elements = findElements(ctx, 'helix'); @@ -42,7 +40,7 @@ function compare_ssr(x: SSElement<SecondaryStructure.Sheet>, y: SSElement<Second 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}`), + CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('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), @@ -53,8 +51,8 @@ const struct_conf_fields: CifField[] = [ ]; const struct_sheet_range_fields: CifField[] = [ - CifField.index('id'), CifField.str<number, SSElement<SecondaryStructure.Sheet>[]>('sheet_id', (i, data) => data[i].element.sheet_id), + CifField.index('id'), ...residueIdFields('beg_', e => e.start), ...residueIdFields('end_', e => e.end), CifField.str('symmetry', (i, data) => '', { valueKind: (i, d) => Column.ValueKind.Unknown }) @@ -66,7 +64,7 @@ function residueIdFields(prefix: string, loc: (e: SSElement<any>) => StructureEl 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}label_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]))) @@ -92,7 +90,7 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex const segs = unit.model.atomicHierarchy.residueAtomSegments; const residues = Segmentation.transientSegments(segs, unit.elements); - let current: Segmentation.Segment<ElementIndex>, move = true; + let current: Segmentation.Segment, move = true; while (residues.hasNext) { if (move) current = residues.move(); diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index c783b552e041369ec50d4466e299357d476119c1..89d18e9b1963eebf2cf0121db230eca462d4056f 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -18,6 +18,7 @@ export interface CifExportContext { import CifCategory = CifWriter.Category import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure'; +import { _pdbx_struct_mod_residue } from './categories/modified-residues'; function copy_mmCif_category(name: keyof mmCIF_Schema) { return ({ model }: CifExportContext) => { @@ -57,6 +58,8 @@ const Categories = [ copy_mmCif_category('chem_comp'), copy_mmCif_category('atom_sites'), + _pdbx_struct_mod_residue, + // Atoms _atom_site ]; diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 46fd62b3ec67f34bb46e2df353eae2f71afccdd1..48faa3d3f3088fa97e0e601ea0bd5b194af08171 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -70,17 +70,20 @@ function getNcsOperators(format: mmCIF_Format) { } return opers; } -function getModifiedResidueNameMap(format: mmCIF_Format) { +function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['modifiedResidues'] { const data = format.data.pdbx_struct_mod_residue; - const map = new Map<string, string>(); + const parentId = new Map<string, string>(); + const details = new Map<string, string>(); const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id; - const parent_id = data.parent_comp_id; + const parent_id = data.parent_comp_id, details_data = data.details; for (let i = 0; i < data._rowCount; i++) { - map.set(comp_id.value(i), parent_id.value(i)); + const id = comp_id.value(i); + parentId.set(id, parent_id.value(i)); + details.set(id, details_data.value(i)); } - return map; + return { parentId, details }; } function getAsymIdSerialMap(format: mmCIF_Format) { @@ -132,7 +135,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities ? format.data.entry.id.value(0) : format.data._name; - const modifiedResidueNameMap = getModifiedResidueNameMap(format); + const modifiedResidues = getModifiedResidueNameMap(format); const asymIdSerialMap = getAsymIdSerialMap(format) const chemicalComponentMap = getChemicalComponentMap(format) @@ -143,14 +146,14 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities modelNum: atom_site.pdbx_PDB_model_num.value(0), entities, symmetry: getSymmetry(format), - sequence: getSequence(format.data, entities, atomic.hierarchy, modifiedResidueNameMap), + sequence: getSequence(format.data, entities, atomic.hierarchy, modifiedResidues.parentId), atomicHierarchy: atomic.hierarchy, atomicConformation: atomic.conformation, coarseHierarchy: coarse.hierarchy, coarseConformation: coarse.conformation, properties: { secondaryStructure: getSecondaryStructureMmCif(format.data, atomic.hierarchy), - modifiedResidueNameMap, + modifiedResidues, asymIdSerialMap, chemicalComponentMap }, @@ -163,7 +166,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities function createModelIHM(format: mmCIF_Format, data: IHMData): Model { const atomic = getAtomicHierarchyAndConformation(format, data.atom_site, data.entities); const coarse = getIHMCoarse(data); - const modifiedResidueNameMap = getModifiedResidueNameMap(format); + const modifiedResidues = getModifiedResidueNameMap(format); const asymIdSerialMap = getAsymIdSerialMap(format) const chemicalComponentMap = getChemicalComponentMap(format) @@ -174,14 +177,14 @@ function createModelIHM(format: mmCIF_Format, data: IHMData): Model { modelNum: data.model_id, entities: data.entities, symmetry: getSymmetry(format), - sequence: getSequence(format.data, data.entities, atomic.hierarchy, modifiedResidueNameMap), + sequence: getSequence(format.data, data.entities, atomic.hierarchy, modifiedResidues.parentId), atomicHierarchy: atomic.hierarchy, atomicConformation: atomic.conformation, coarseHierarchy: coarse.hierarchy, coarseConformation: coarse.conformation, properties: { secondaryStructure: getSecondaryStructureMmCif(format.data, atomic.hierarchy), - modifiedResidueNameMap, + modifiedResidues, asymIdSerialMap, chemicalComponentMap }, 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 21761059d9308730ab07eaf1e7b29c60a1555441..1cdbcb6be294603128c847379e1c885ae60ad6ee 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts +++ b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts @@ -141,7 +141,7 @@ export namespace ComponentBond { loc.unit = unit; while (residues.hasNext) { const seg = residues.move(); - loc.element = seg.start; + loc.element = unit.elements[seg.start]; names.add(prop(loc)); } } diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 4ffd21aac6f6cb8f6b3e1dfa21d6cedc49475025..03c962fde8304a20953a3d7906173f45d4d32de8 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -42,7 +42,10 @@ export interface Model extends Readonly<{ /** secondary structure provided by the input file */ readonly secondaryStructure: SecondaryStructure, /** maps modified residue name to its parent */ - readonly modifiedResidueNameMap: Map<string, string>, + readonly modifiedResidues: { + parentId: Map<string, string>, + details: Map<string, string> + }, /** maps asym id to unique serial number */ readonly asymIdSerialMap: Map<string, number> /** maps residue name to `ChemicalComponent` data */ diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts index ecb4cea9ef649cc4c03057d010903d421627002b..9000205da2eb9c4aaa1c77141abdde5adecba0ac 100644 --- a/src/mol-model/structure/query/generators.ts +++ b/src/mol-model/structure/query/generators.ts @@ -7,7 +7,7 @@ import Query from './query' import Selection from './selection' import { StructureElement, Unit, StructureProperties as P } from '../structure' -import { OrderedSet, Segmentation } from 'mol-data/int' +import { Segmentation } from 'mol-data/int' import { LinearGroupingBuilder } from './utils/builders'; export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s); @@ -87,20 +87,20 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (chainsIt.hasNext) { const chainSegment = chainsIt.move(); - l.element = OrderedSet.getAt(elements, chainSegment.start); + l.element = elements[chainSegment.start]; // test entity and chain if (!entityTest(l) || !chainTest(l)) continue; residuesIt.setSegment(chainSegment); while (residuesIt.hasNext) { const residueSegment = residuesIt.move(); - l.element = OrderedSet.getAt(elements, residueSegment.start); + l.element = elements[residueSegment.start]; // test residue if (!residueTest(l)) continue; for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { - l.element = OrderedSet.getAt(elements, j); + l.element = elements[j]; if (atomTest(l)) { builder.addElement(l.element); } @@ -134,20 +134,20 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (chainsIt.hasNext) { const chainSegment = chainsIt.move(); - l.element = OrderedSet.getAt(elements, chainSegment.start); + l.element = elements[chainSegment.start]; // test entity and chain if (!entityTest(l) || !chainTest(l)) continue; residuesIt.setSegment(chainSegment); while (residuesIt.hasNext) { const residueSegment = residuesIt.move(); - l.element = OrderedSet.getAt(elements, residueSegment.start); + l.element = elements[residueSegment.start]; // test residue if (!residueTest(l)) continue; for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { - l.element = OrderedSet.getAt(elements, j); + l.element = elements[j]; if (atomTest(l)) builder.add(groupBy(l), unit.id, l.element); } }