From f10a135deac9bf1200b85c6c844f9cadc798429a Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Sat, 22 Jun 2019 07:24:01 -0700
Subject: [PATCH] sequence view, better handling of missing residues, full
 structure loci

---
 src/mol-data/int/ordered-set.ts       |  1 +
 src/mol-data/int/sorted-array.ts      |  8 +--
 src/mol-plugin/ui/sequence/polymer.ts | 76 ++++++++++++++++-----------
 src/mol-plugin/ui/sequence/util.ts    | 14 ++---
 4 files changed, 56 insertions(+), 43 deletions(-)

diff --git a/src/mol-data/int/ordered-set.ts b/src/mol-data/int/ordered-set.ts
index 7f671e22a..4ec45b17f 100644
--- a/src/mol-data/int/ordered-set.ts
+++ b/src/mol-data/int/ordered-set.ts
@@ -37,6 +37,7 @@ namespace OrderedSet {
 
     export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any;
     export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any;
+    /** Returns elements of `a` that are not in `b`, i.e `a` - `b` */
     export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
 
     export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts
index bd1c202b0..8858aed3d 100644
--- a/src/mol-data/int/sorted-array.ts
+++ b/src/mol-data/int/sorted-array.ts
@@ -12,9 +12,9 @@ namespace SortedArray {
     export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any;
     export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any;
     export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any;
-    // create sorted array [min, max] (it DOES contain the max value)
+    /** create sorted array [min, max] (it DOES contain the max value) */
     export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any;
-    // create sorted array [min, max) (it does NOT contain the max value)
+    /** create sorted array [min, max) (it does NOT contain the max value) */
     export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
     export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
 
@@ -23,9 +23,9 @@ namespace SortedArray {
     export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
     export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
 
-    // array[0]
+    /** Returns `array[0]` */
     export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any;
-    // array[array.length - 1] + 1
+    /** Returns `array[array.length - 1] + 1` */
     export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any;
     export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any;
     export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
diff --git a/src/mol-plugin/ui/sequence/polymer.ts b/src/mol-plugin/ui/sequence/polymer.ts
index 03b1f60d0..7ccd38272 100644
--- a/src/mol-plugin/ui/sequence/polymer.ts
+++ b/src/mol-plugin/ui/sequence/polymer.ts
@@ -6,7 +6,7 @@
 
 import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit } from '../../../mol-model/structure';
 import { SequenceWrapper } from './util';
-import { OrderedSet, Interval } from '../../../mol-data/int';
+import { OrderedSet, Interval, SortedArray } from '../../../mol-data/int';
 import { Loci } from '../../../mol-model/loci';
 import { Sequence } from '../../../mol-model/sequence';
 import { MissingResidues } from '../../../mol-model/structure/model/properties/common';
@@ -18,46 +18,52 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     private readonly location: StructureElement
     private readonly sequence: Sequence
     private readonly missing: MissingResidues
+    private readonly observed: OrderedSet // sequences indices
 
     private readonly modelNum: number
     private readonly asymId: string
 
-    seqId(i: number) {
-        return this.sequence.offset + i + 1
+    seqId(seqIdx: number) {
+        return this.sequence.offset + seqIdx + 1
     }
-    residueLabel(i: number) {
-        return this.sequence.sequence[i]
+    residueLabel(seqIdx: number) {
+        return this.sequence.sequence[seqIdx]
     }
-    residueColor(i: number) {
-        return this.missing.has(this.modelNum, this.asymId, this.seqId(i))
+    residueColor(seqIdx: number) {
+        return this.missing.has(this.modelNum, this.asymId, this.seqId(seqIdx))
             ? ColorNames.grey
             : ColorNames.black
     }
 
-    eachResidue(loci: Loci, apply: (interval: Interval) => boolean) {
+    eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean) {
         let changed = false
         const { structure, unit } = this.data
-        if (!StructureElement.isLoci(loci)) return false
-        if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
-
-        const { location } = this
-        for (const e of loci.elements) {
-            let rIprev = -1
-            location.unit = e.unit
-
-            const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments
-
-            OrderedSet.forEach(e.indices, v => {
-                location.element = e.unit.elements[v]
-                const rI = residueIndex[location.element]
-                // avoid checking for the same residue multiple times
-                if (rI !== rIprev) {
-                    if (SP.unit.id(location) !== unit.id) return
-
-                    if (apply(getSeqIdInterval(location))) changed = true
-                    rIprev = rI
-                }
-            })
+        if (StructureElement.isLoci(loci)) {
+            if (!Structure.areParentsEqual(loci.structure, structure)) return false
+
+            const { location } = this
+            for (const e of loci.elements) {
+                let rIprev = -1
+                location.unit = e.unit
+
+                const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments
+
+                OrderedSet.forEach(e.indices, v => {
+                    location.element = e.unit.elements[v]
+                    const rI = residueIndex[location.element]
+                    // avoid checking for the same residue multiple times
+                    if (rI !== rIprev) {
+                        if (SP.unit.id(location) !== unit.id) return
+
+                        if (apply(getSeqIndices(location))) changed = true
+                        rIprev = rI
+                    }
+                })
+            }
+        } else if (Structure.isLoci(loci)) {
+            if (!Structure.areParentsEqual(loci.structure, structure)) return false
+
+            if (apply(this.observed)) changed = true
         }
         return changed
     }
@@ -70,7 +76,8 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     constructor(data: StructureUnit) {
         const l = StructureElement.create(data.unit, data.unit.elements[0])
         const sequence = data.unit.model.sequence.byEntityKey[SP.entity.key(l)].sequence
-        const markerArray = new Uint8Array(sequence.sequence.length)
+        const length = sequence.sequence.length
+        const markerArray = new Uint8Array(length)
 
         super(data, markerArray, sequence.sequence.length)
 
@@ -80,6 +87,12 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
 
         this.modelNum = data.unit.model.modelNum
         this.asymId = SP.chain.label_asym_id(l)
+
+        const missing: number[] = []
+        for (let i = 0; i < length; ++i) {
+            if (this.missing.has(this.modelNum, this.asymId, this.seqId(i))) missing.push(i)
+        }
+        this.observed = OrderedSet.subtract(Interval.ofBounds(0, length),  SortedArray.ofSortedArray(missing))
     }
 }
 
@@ -101,8 +114,7 @@ function createResidueQuery(unitId: number, label_seq_id: number) {
     });
 }
 
-/** Zero-indexed */
-function getSeqIdInterval(location: StructureElement): Interval {
+function getSeqIndices(location: StructureElement): Interval {
     const { unit, element } = location
     const { model } = unit
     switch (unit.kind) {
diff --git a/src/mol-plugin/ui/sequence/util.ts b/src/mol-plugin/ui/sequence/util.ts
index 91813be7a..158b47294 100644
--- a/src/mol-plugin/ui/sequence/util.ts
+++ b/src/mol-plugin/ui/sequence/util.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Interval } from '../../../mol-data/int';
+import { OrderedSet } from '../../../mol-data/int';
 import { Loci } from '../../../mol-model/loci';
 import { MarkerAction, applyMarkerAction } from '../../../mol-util/marker-action';
 import { StructureElement } from '../../../mol-model/structure';
@@ -13,16 +13,16 @@ import { Color } from '../../../mol-util/color';
 export { SequenceWrapper }
 
 abstract class SequenceWrapper<D> {
-    abstract seqId(i: number): number
-    abstract residueLabel(i: number): string
-    abstract residueColor(i: number): Color
+    abstract seqId(seqIdx: number): number
+    abstract residueLabel(seqIdx: number): string
+    abstract residueColor(seqIdx: number): Color
 
-    abstract eachResidue(loci: Loci, apply: (interval: Interval) => boolean): boolean
+    abstract eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean): boolean
     abstract getLoci(seqId: number): StructureElement.Loci
 
     markResidue(loci: Loci, action: MarkerAction) {
-        return this.eachResidue(loci, (i: Interval) => {
-            return applyMarkerAction(this.markerArray, i, action)
+        return this.eachResidue(loci, (set: OrderedSet) => {
+            return applyMarkerAction(this.markerArray, set, action)
         })
     }
 
-- 
GitLab