From 0f63ae0eacb7cd4ff6e3e413d32c01d5f6af47e2 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Wed, 11 Jul 2018 11:55:41 +0200
Subject: [PATCH] wip, strongly type model indexing

---
 src/mol-data/int/segmentation.ts              | 18 ++++++------
 src/mol-model/structure/model.ts              |  1 +
 .../formats/mmcif/secondary-structure.ts      |  5 ++--
 src/mol-model/structure/model/indexing.ts     | 10 +++++++
 src/mol-model/structure/model/model.ts        |  8 +----
 .../model/properties/atomic/hierarchy.ts      | 29 +++++++------------
 .../structure/model/properties/common.ts      |  3 +-
 .../structure/model/properties/sequence.ts    |  7 +++--
 .../model/properties/utils/atomic-keys.ts     | 17 ++++++-----
 .../structure/structure/properties.ts         |  6 ++--
 src/mol-model/structure/structure/unit.ts     |  5 ++--
 11 files changed, 56 insertions(+), 53 deletions(-)
 create mode 100644 src/mol-model/structure/model/indexing.ts

diff --git a/src/mol-data/int/segmentation.ts b/src/mol-data/int/segmentation.ts
index 6007a20c2..2afdd6a55 100644
--- a/src/mol-data/int/segmentation.ts
+++ b/src/mol-data/int/segmentation.ts
@@ -9,25 +9,25 @@ import OrderedSet from './ordered-set'
 import * as Impl from './impl/segmentation'
 
 namespace Segmentation {
-    export interface Segment<T extends number = number> { index: number, start: T, end: T }
+    export interface Segment<T extends number = number, I extends number = number> { index: number, start: T, end: T }
 
-    export const create: <T extends number = number>(segs: ArrayLike<T>) => Segmentation<T> = Impl.create as any;
-    export const ofOffsets: <T extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T> = Impl.ofOffsets as any;
+    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;
 
-    export const count: <T extends number = number>(segs: Segmentation<T>) => number = Impl.count as any;
-    export const getSegment: <T extends number = number>(segs: Segmentation<T>, value: T) => number = Impl.getSegment as any;
-    export const projectValue: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
+    export const count: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>) => number = Impl.count as any;
+    export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
+    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>(segs: Segmentation<T>, 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<T>) => Impl.SegmentIterator<T> = Impl.segments as any;
 }
 
-interface Segmentation<T extends number = number> {
+interface Segmentation<T extends number = number, I extends number = number> {
     '@type': 'segmentation',
     /** All segments are defined by offsets [offsets[i], offsets[i + 1]) for i \in [0, count - 1] */
     readonly offsets: ArrayLike<T>,
     /** Segment index of the i-th element */
-    readonly index: ArrayLike<number>,
+    readonly index: ArrayLike<I>,
     readonly count: number
 }
 
diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts
index af5b8c7bb..c208d036d 100644
--- a/src/mol-model/structure/model.ts
+++ b/src/mol-model/structure/model.ts
@@ -10,4 +10,5 @@ import Format from './model/format'
 import { ModelSymmetry } from './model/properties/symmetry'
 import StructureSequence from './model/properties/sequence'
 
+export * from './model/indexing'
 export { Model, Types, Format, ModelSymmetry, StructureSequence }
\ No newline at end of file
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 2e181f668..d871052c9 100644
--- a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
+++ b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
@@ -10,6 +10,7 @@ import { SecondaryStructureType } from '../../types';
 import { AtomicHierarchy } from '../../properties/atomic';
 import { SecondaryStructure } from '../../properties/seconday-structure';
 import { Column } from 'mol-data/db';
+import { ChainIndex, ResidueIndex } from '../../indexing';
 
 export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
     const map: SecondaryStructureMap = new Map();
@@ -130,7 +131,7 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap,
     return;
 }
 
-function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) {
+function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: ResidueIndex, resEnd: ResidueIndex, data: SecondaryStructureData) {
     const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
     const { endSeqNumber, endInsCode, key, type } = entry;
 
@@ -154,7 +155,7 @@ function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: Seconda
     const { label_asym_id } = hierarchy.chains;
     const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
 
-    for (let cI = 0; cI < chainCount; cI++) {
+    for (let cI = 0 as ChainIndex; cI < chainCount; cI++) {
         const resStart = AtomicHierarchy.chainStartResidueIndex(hierarchy, cI), resEnd = AtomicHierarchy.chainEndResidueIndexExcl(hierarchy, cI);
         const asymId = label_asym_id.value(cI);
         if (map.has(asymId)) {
diff --git a/src/mol-model/structure/model/indexing.ts b/src/mol-model/structure/model/indexing.ts
new file mode 100644
index 000000000..65dfce091
--- /dev/null
+++ b/src/mol-model/structure/model/indexing.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export type ElementIndex = { readonly '@type': 'element-index' } & number
+export type ResidueIndex = { readonly '@type': 'residue-index' } & number
+export type ChainIndex = { readonly '@type': 'chain-index' } & number
+export type EntityIndex = { readonly '@type': 'entity-index' } & number
\ 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 412531f73..4ffd21aac 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -71,10 +71,4 @@ export namespace Model {
             case 'mmCIF': return from_mmCIF(format);
         }
     }
-}
-
-
-export type ElementIndex = { readonly '@type': 'element-index' } & number
-export type ResidueIndex = { readonly '@type': 'residue-index' } & number
-export type ChainIndex = { readonly '@type': 'chain-index' } & number
-export type EntityIndex = { readonly '@type': 'entity-index' } & number
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index a4aeaacad..0a924c80d 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -9,6 +9,7 @@ import { Segmentation } from 'mol-data/int'
 import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 import { ElementSymbol } from '../../types'
 import { Element } from '../../../structure'
+import { ChainIndex, EntityIndex, ResidueIndex } from '../../indexing';
 
 export const AtomsSchema = {
     type_symbol: Column.Schema.Aliased<ElementSymbol>(mmCIF.atom_site.type_symbol),
@@ -49,7 +50,7 @@ export interface AtomicData {
 
 export interface AtomicSegments {
     /** Maps residueIndex to a range of atoms [segments[rI], segments[rI + 1]) */
-    residueAtomSegments: Segmentation<Element>,
+    residueAtomSegments: Segmentation<Element, ResidueIndex>,
     /**
      * Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]),
      *
@@ -58,7 +59,7 @@ export interface AtomicSegments {
      * const start = rI[offsets[i]], const end = rI[offsets[i + 1] - 1] + 1;
      * for (let j = start; j < end; i++) { }
      */
-    chainAtomSegments: Segmentation<Element>,
+    chainAtomSegments: Segmentation<Element, ChainIndex>,
     /**
      * bonded/connected stretches of polymer chains, i.e. a chain will be
      * broken into multiple polymer segments if there are missing residues
@@ -69,27 +70,19 @@ export interface AtomicSegments {
 }
 
 export interface AtomicKeys {
-    // TODO: since Atoms must be sorted now, get rid of keys
     // TODO: include (lazily computed) "entity/chain/residue" indices?
 
-    // assign a key to each residue index.
-    residueKey: ArrayLike<number>,
-    // assign a key to each chain index
-    chainKey: ArrayLike<number>,
-    // assigne a key to each chain index
-    // also index to the Entities table.
-    entityKey: ArrayLike<number>,
+    /** @returns index or -1 if not present. */
+    getEntityKey(cI: ChainIndex): EntityIndex,
 
-    /**
-     * @returns index or -1 if not present.
-     */
-    findChainKey(entityId: string, label_asym_id: string): number,
+    /** @returns index or -1 if not present. */
+    findChainKey(entityId: string, label_asym_id: string): ChainIndex,
 
     /**
      * Unique number for each of the residue. Also the index of the 1st occurence of this residue.
      * @returns index or -1 if not present.
      */
-    findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
+    findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): ResidueIndex
 }
 
 type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys
@@ -97,12 +90,12 @@ export interface AtomicHierarchy extends _Hierarchy { }
 
 export namespace AtomicHierarchy {
     /** Start residue inclusive */
-    export function chainStartResidueIndex(segs: AtomicSegments, cI: number) {
+    export function chainStartResidueIndex(segs: AtomicSegments, cI: ChainIndex) {
         return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI]];
     }
 
     /** End residue exclusive */
-    export function chainEndResidueIndexExcl(segs: AtomicSegments, cI: number) {
-        return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI + 1] - 1] + 1;
+    export function chainEndResidueIndexExcl(segs: AtomicSegments, cI: ChainIndex) {
+        return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI + 1] - 1] + 1 as ResidueIndex;
     }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/common.ts b/src/mol-model/structure/model/properties/common.ts
index 35d9143ae..c1b7af14d 100644
--- a/src/mol-model/structure/model/properties/common.ts
+++ b/src/mol-model/structure/model/properties/common.ts
@@ -5,8 +5,9 @@
  */
 
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
+import { EntityIndex } from '../indexing';
 
 export interface Entities {
     data: mmCIF['entity'],
-    getEntityIndex(id: string): import('../model').EntityIndex
+    getEntityIndex(id: string): EntityIndex
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts
index ec7b5da76..b29ead3f5 100644
--- a/src/mol-model/structure/model/properties/sequence.ts
+++ b/src/mol-model/structure/model/properties/sequence.ts
@@ -8,6 +8,7 @@ import { Column } from 'mol-data/db'
 import { AtomicHierarchy } from './atomic/hierarchy';
 import { Entities } from './common';
 import { Sequence } from '../../../sequence';
+import { ChainIndex } from '../indexing';
 
 interface StructureSequence {
     readonly sequences: ReadonlyArray<StructureSequence.Entity>,
@@ -30,14 +31,14 @@ namespace StructureSequence {
         const byEntityKey: StructureSequence['byEntityKey'] = { };
         const sequences: StructureSequence.Entity[] = [];
 
-        for (let cI = 0, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
-            const entityKey = hierarchy.entityKey[cI];
+        for (let cI = 0 as ChainIndex, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
+            const entityKey = hierarchy.getEntityKey(cI);
             // Only for polymers, trying to mirror _entity_poly_seq
             if (byEntityKey[entityKey] !== void 0 || entities.data.type.value(entityKey) !== 'polymer') continue;
 
             let start = cI;
             cI++;
-            while (cI < _cI && entityKey === hierarchy.entityKey[cI] && entities.data.type.value(entityKey) !== 'polymer') {
+            while (cI < _cI && entityKey === hierarchy.getEntityKey(cI) && entities.data.type.value(entityKey) !== 'polymer') {
                 cI++;
             }
             cI--;
diff --git a/src/mol-model/structure/model/properties/utils/atomic-keys.ts b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
index 0fdf1c8e5..de4029984 100644
--- a/src/mol-model/structure/model/properties/utils/atomic-keys.ts
+++ b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
@@ -7,6 +7,7 @@
 import { AtomicData, AtomicSegments, AtomicKeys } from '../atomic'
 import { Interval, Segmentation } from 'mol-data/int'
 import { Entities } from '../common'
+import { ChainIndex, ResidueIndex, EntityIndex } from '../../indexing';
 
 function getResidueId(comp_id: string, seq_id: number, ins_code: string) {
     return `${comp_id} ${seq_id} ${ins_code}`;
@@ -30,20 +31,20 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
     const getEntKey = entities.getEntityIndex;
     const findChainKey: AtomicKeys['findChainKey'] = (e, c) => {
         let eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ChainIndex;
         const cm = chain.get(eKey)!;
-        if (!cm.has(c)) return -1;
-        return cm.get(c)!;
+        if (!cm.has(c)) return -1 as ChainIndex;
+        return cm.get(c)! as ChainIndex;
     }
     const findResidueKey: AtomicKeys['findResidueKey'] = (e, c, name, seq, ins) => {
         let eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ResidueIndex;
         const cm = chain.get(eKey)!;
-        if (!cm.has(c)) return -1;
+        if (!cm.has(c)) return -1 as ResidueIndex;
         const rm = residue.get(cm.get(c)!)!
         const id = getResidueId(name, seq, ins);
-        if (!rm.has(id)) return -1;
-        return rm.get(id)!;
+        if (!rm.has(id)) return -1 as ResidueIndex;
+        return rm.get(id)! as ResidueIndex;
     }
     return { findChainKey, findResidueKey };
 }
@@ -92,5 +93,5 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
 
     const { findChainKey, findResidueKey } = createLookUp(entities, chainMaps, residueMaps);
 
-    return { residueKey, chainKey, entityKey, findChainKey, findResidueKey };
+    return { getEntityKey: cI => entityKey[cI] as EntityIndex, findChainKey, findResidueKey };
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts
index d13f5ddd8..181e8b6b7 100644
--- a/src/mol-model/structure/structure/properties.ts
+++ b/src/mol-model/structure/structure/properties.ts
@@ -48,7 +48,7 @@ const atom = {
 }
 
 const residue = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
 
     group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
     label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
@@ -63,7 +63,7 @@ const residue = {
 }
 
 const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]),
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.chainIndex[l.element]),
 
     label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
     auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
@@ -92,7 +92,7 @@ const coarse = {
 function eK(l: Element.Location) {
     switch (l.unit.kind) {
         case Unit.Kind.Atomic:
-            return l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]
+            return l.unit.model.atomicHierarchy.getEntityKey(l.unit.chainIndex[l.element])
         case Unit.Kind.Spheres:
             return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
         case Unit.Kind.Gaussians:
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index 14d0fc818..f5b9f1c4c 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -13,6 +13,7 @@ import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation }
 import { ValueRef } from 'mol-util';
 import { UnitRings } from './unit/rings';
 import Element from './element'
+import { ChainIndex, ResidueIndex } from '../model/indexing';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -82,8 +83,8 @@ namespace Unit {
         readonly conformation: SymmetryOperator.ArrayMapping;
 
         // Reference some commonly accessed things for faster access.
-        readonly residueIndex: ArrayLike<number>;
-        readonly chainIndex: ArrayLike<number>;
+        readonly residueIndex: ArrayLike<ResidueIndex>;
+        readonly chainIndex: ArrayLike<ChainIndex>;
 
         private props: AtomicProperties;
 
-- 
GitLab