From 0b9f31b45e79fd5d3931894cf8e36ce1382cc7f1 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Fri, 8 Jun 2018 12:58:51 +0200
Subject: [PATCH] Mod res support

---
 src/apps/structure-info/model.ts              | 12 ++++
 .../structure/model/formats/mmcif.ts          | 17 ++++-
 .../model/formats/mmcif/modified-residues.ts  | 65 +++++++++++++++++++
 src/mol-model/structure/model/model.ts        |  7 +-
 .../model/properties/atomic/hierarchy.ts      |  4 +-
 5 files changed, 101 insertions(+), 4 deletions(-)
 create mode 100644 src/mol-model/structure/model/formats/mmcif/modified-residues.ts

diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index 85f118dea..384758e57 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -126,6 +126,17 @@ export function printSequence(model: Model) {
     console.log();
 }
 
+export function printModRes(model: Model) {
+    console.log('\nModified Residues\n=============');
+    const { residueIndices, keys, data } = model.properties.modifiedResidues;
+    const { label_comp_id } = model.atomicHierarchy.residues;
+    for (let i = 0; i < residueIndices.length; i++) {
+        const rI = residueIndices[i], k = keys[i];
+        console.log(`Idx: ${rI} ${data.parent_comp_id.value(k)} -> ${label_comp_id.value(rI)}`);
+    }
+    console.log();
+}
+
 export function printRings(structure: Structure) {
     console.log('\nRings\n=============');
     for (const unit of structure.units) {
@@ -186,6 +197,7 @@ async function run(mmcif: mmCIF_Database) {
     printUnits(structure);
     printRings(structure);
     printLinks(structure, false, true);
+    printModRes(models[0]);
     //printSecStructure(models[0]);
 }
 
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index a50d29311..7cbaedb7d 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -11,7 +11,7 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import UUID from 'mol-util/uuid';
 import Format from '../format';
 import Model from '../model';
-import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic';
+import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema, AtomicHierarchy } from '../properties/atomic';
 import { Entities } from '../properties/common';
 import { ModelSymmetry } from '../properties/symmetry';
 import { getAtomicKeys } from '../properties/utils/atomic-keys';
@@ -23,6 +23,7 @@ import { getSequence } from './mmcif/sequence';
 import mmCIF_Format = Format.mmCIF
 import { Task } from 'mol-task';
 import { getSecondaryStructureMmCif } from './mmcif/secondary-structure';
+import { ModifiedResidues } from './mmcif/modified-residues';
 
 function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
@@ -118,6 +119,15 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
         && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
 }
 
+function modResProvider(format: mmCIF_Format, hierarchy: AtomicHierarchy, entities: Entities) {
+    let modres: ModifiedResidues | undefined = void 0;
+    return () => {
+        if (modres) return modres;
+        modres = new ModifiedResidues(format.data.pdbx_struct_mod_residue, hierarchy, entities);
+        return modres;
+    }
+}
+
 function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): Model {
     const hierarchyOffsets = findHierarchyOffsets(format, bounds);
     const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets);
@@ -146,6 +156,8 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
         ? format.data.entry.id.value(0)
         : format.data._name;
 
+    const modRes = modResProvider(format, atomicHierarchy, entities);
+
     return {
         id: UUID.create(),
         label,
@@ -158,7 +170,8 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
         coarseHierarchy: coarse.hierarchy,
         coarseConformation: coarse.conformation,
         properties: {
-            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy)
+            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy),
+            get modifiedResidues() { return modRes() }
         },
         symmetry: getSymmetry(format)
     };
diff --git a/src/mol-model/structure/model/formats/mmcif/modified-residues.ts b/src/mol-model/structure/model/formats/mmcif/modified-residues.ts
new file mode 100644
index 000000000..64f182b2b
--- /dev/null
+++ b/src/mol-model/structure/model/formats/mmcif/modified-residues.ts
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
+import { AtomicHierarchy } from '../../properties/atomic';
+import { Entities } from '../../properties/common';
+
+class ModifiedResidues {
+    private residueIndexMap = new Map<number, number>();
+
+    /** All residue indices within the given model that are modified. */
+    readonly residueIndices: ReadonlyArray<number>;
+    /** Indexed same as residueIndex and is key to ModifiedResidues.data table */
+    readonly keys: ReadonlyArray<number>;
+
+    /** Index into the data table. -1 if the residue is not modified. */
+    getKey(residueIndex: number): number {
+        return this.residueIndexMap.has(residueIndex) ? this.residueIndexMap.get(residueIndex)! : -1;
+    }
+
+    constructor(public data: mmCIF_Database['pdbx_struct_mod_residue'], hierarchy: AtomicHierarchy, entities: Entities) {
+        if (data._rowCount === 0) {
+            this.residueIndices = [];
+            this.keys = [];
+            return;
+        }
+
+        const { PDB_ins_code, auth_seq_id, _rowCount } = data;
+
+        const asym_id = data.label_asym_id.isDefined ? data.label_asym_id : data.auth_asym_id;
+        const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id;
+
+        const entityIds = entities.data.id.toArray();
+
+        const residueIndices: number[] = [];
+        const keys: number[] = [];
+
+        for (let i = 0; i < _rowCount; i++) {
+            const aId = asym_id.value(i);
+            const eIdx = getEntityId(hierarchy, entityIds, aId);
+            if (eIdx < 0) continue;
+            const key = hierarchy.findResidueKey(entityIds[eIdx], aId, comp_id.value(i), auth_seq_id.value(i), PDB_ins_code.value(i));
+            if (key >= 0) {
+                this.residueIndexMap.set(key, i);
+                residueIndices[residueIndices.length] = key;
+                keys[keys.length] = i;
+            }
+        }
+
+        this.residueIndices = residueIndices;
+        this.keys = keys;
+    }
+}
+
+function getEntityId(hierarchy: AtomicHierarchy, ids: ArrayLike<string>, asym_id: string) {
+    for (let i = 0, _i = ids.length; i < _i; i++) {
+        if (hierarchy.findChainKey(ids[i], asym_id) >= 0) return i;
+    }
+    return -1;
+}
+
+export { ModifiedResidues }
\ 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 a20eaf796..8f60531e6 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -15,6 +15,7 @@ import { SecondaryStructure } from './properties/seconday-structure';
 
 //import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
+import { ModifiedResidues } from './formats/mmcif/modified-residues';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -37,7 +38,11 @@ interface Model extends Readonly<{
     atomicConformation: AtomicConformation,
 
     /** Various parts of the code can "cache" custom properties here */
-    properties: { readonly secondaryStructure: SecondaryStructure } & { [customName: string]: any },
+    properties: {
+        readonly secondaryStructure: SecondaryStructure,
+        readonly modifiedResidues: ModifiedResidues,
+        [customName: string]: any
+    },
 
     coarseHierarchy: CoarseHierarchy,
     coarseConformation: CoarseConformation
diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index fe50e990b..4327d22d4 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -65,7 +65,9 @@ export interface AtomicKeys {
     // also index to the Entities table.
     entityKey: ArrayLike<number>,
 
-    findChainKey(entityId: string, label_asym_id: string): number
+    findChainKey(entityId: string, label_asym_id: string): number,
+
+    /** Unique number for each of the residue. Also the index of the 1st occurence of this residue. */
     findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
 }
 
-- 
GitLab