Bladeren bron

added StructureElement.Query for Loci serialization

Alexander Rose 5 jaren geleden
bovenliggende
commit
df0f15d132
2 gewijzigde bestanden met toevoegingen van 176 en 4 verwijderingen
  1. 1 1
      src/mol-data/util/hash-functions.ts
  2. 175 3
      src/mol-model/structure/structure/element.ts

+ 1 - 1
src/mol-data/util/hash-functions.ts

@@ -73,7 +73,7 @@ export function sortedCantorPairing(a: number, b: number) {
 /**
  * 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
  */
-export function hashFnv32a(array: number[]) {
+export function hashFnv32a(array: ArrayLike<number>) {
     let hval = 0x811c9dc5;
     for (let i = 0, il = array.length; i < il; ++i) {
         hval ^= array[i];

+ 175 - 3
src/mol-model/structure/structure/element.ts

@@ -6,7 +6,7 @@
  */
 
 import { UniqueArray } from '../../../mol-data/generic';
-import { OrderedSet, SortedArray } from '../../../mol-data/int';
+import { OrderedSet, SortedArray, Interval } from '../../../mol-data/int';
 import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
@@ -16,8 +16,9 @@ import Structure from './structure';
 import Unit from './unit';
 import { Boundary } from './util/boundary';
 import { StructureProperties } from '../structure';
-import { sortArray, hashFnv32a, hash2 } from '../../../mol-data/util';
+import { sortArray, hashFnv32a, hash2, hash3 } from '../../../mol-data/util';
 import Expression from '../../../mol-script/language/expression';
+import SortedRanges from '../../../mol-data/int/sorted-ranges';
 
 interface StructureElement<U = Unit> {
     readonly kind: 'element-location',
@@ -115,7 +116,7 @@ namespace StructureElement {
 
     export function entityIndex(l: StructureElement) {
         return StructureProperties.entity.key(l)
-        }
+    }
 
     export namespace Loci {
         export function size(loci: Loci) {
@@ -461,6 +462,177 @@ namespace StructureElement {
 
         }
     }
+
+    interface QueryElement {
+        /** Array of `Unit.id`s that share the same `Unit.invariantId` */
+        units: SortedArray<number>,
+        set: SortedArray<UnitIndex>
+        ranges: SortedRanges<UnitIndex>
+    }
+    export interface Query {
+        readonly elements: ReadonlyArray<Readonly<QueryElement>>
+    }
+
+    export namespace Query {
+        export const Empty: Query = { elements: [] }
+
+        export function fromLoci(loci: StructureElement.Loci): Query {
+            const _elements: {
+                unit: Unit
+                set: SortedArray<UnitIndex>
+                ranges: SortedRanges<UnitIndex>
+            }[] = []
+            for (const e of loci.elements) {
+                const { unit, indices } = e
+
+                const ranges: UnitIndex[] = [];
+                const set: UnitIndex[] = [];
+
+                if (OrderedSet.isInterval(indices)) {
+                    ranges[ranges.length] = Interval.min(indices)
+                    ranges[ranges.length] = Interval.max(indices)
+                } else {
+                    let i = 0, len = indices.length;
+                    while (i < len) {
+                        const start = i;
+                        i++;
+                        while (i < len && indices[i - 1] + 1 === indices[i]) i++;
+                        const end = i;
+                        // TODO: is this a good value?
+                        if (end - start > 12) {
+                            ranges[ranges.length] = indices[start];
+                            ranges[ranges.length] = indices[end - 1];
+                        } else {
+                            for (let j = start; j < end; j++) {
+                                set[set.length] = indices[j];
+                            }
+                        }
+                    }
+                }
+
+                _elements.push({
+                    unit,
+                    set: SortedArray.ofSortedArray(set),
+                    ranges: SortedRanges.ofSortedRanges(ranges)
+                })
+            }
+
+            const elementGroups = new Map<number, {
+                units: number[]
+                set: SortedArray<UnitIndex>
+                ranges: SortedRanges<UnitIndex>
+            }>();
+            for (let i = 0, il = _elements.length; i < il; ++i) {
+                const e = _elements[i]
+                const key = hash3(hashFnv32a(e.ranges), hashFnv32a(e.set), e.unit.invariantId)
+                if (elementGroups.has(key)) {
+                    elementGroups.get(key)!.units.push(e.unit.id)
+                } else {
+                    elementGroups.set(key, {
+                        units: [e.unit.id],
+                        set: e.set,
+                        ranges: e.ranges
+                    })
+                }
+            }
+
+            const elements: QueryElement[] = []
+            elementGroups.forEach(e => {
+                elements.push({
+                    units: SortedArray.ofUnsortedArray(e.units),
+                    set: e.set,
+                    ranges: e.ranges
+                })
+            })
+
+            return { elements }
+        }
+
+        function getUnitsFromIds(unitIds: ArrayLike<number>, structure: Structure) {
+            const units: Unit[] = []
+            for (let i = 0, il = unitIds.length; i < il; ++i) {
+                const unitId = unitIds[i]
+                if (structure.unitMap.has(unitId)) units.push(structure.unitMap.get(unitId))
+            }
+            return units
+        }
+
+        export function toLoci(query: Query, parent: Structure): Loci {
+            const elements: Loci['elements'][0][] = []
+            for (const e of query.elements) {
+                const units = getUnitsFromIds(e.units, parent)
+                if (units.length === 0) continue
+
+                let indices: OrderedSet<UnitIndex>
+                if (e.ranges.length === 0) {
+                    indices = e.set
+                } else if (e.set.length === 0) {
+                    if (e.ranges.length === 2) {
+                        indices = Interval.ofRange(e.ranges[0], e.ranges[1])
+                    } else {
+                        const _indices = new Int32Array(SortedRanges.size(e.ranges))
+                        SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
+                        indices = SortedArray.ofSortedArray(_indices)
+                    }
+                } else {
+                    const rangesSize = SortedRanges.size(e.ranges)
+                    const _indices = new Int32Array(e.set.length + rangesSize)
+                    SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
+                    _indices.set(e.set, rangesSize)
+                    indices = SortedArray.ofUnsortedArray(_indices) // requires sort
+                }
+
+                for (const unit of units) {
+                    elements.push({ unit, indices })
+                }
+            }
+            return Loci(parent, elements)
+        }
+
+        export function toStructure(query: Query, parent: Structure): Structure {
+            const units: Unit[] = []
+            for (const e of query.elements) {
+                const _units = getUnitsFromIds(e.units, parent)
+                if (_units.length === 0) continue
+
+                const unit = _units[0] // the elements are grouped by unit.invariantId
+                const rangesSize = SortedRanges.size(e.ranges)
+                const _indices = new Int32Array(e.set.length + rangesSize)
+                let indices: SortedArray<ElementIndex>
+                if (e.ranges.length === 0) {
+                    for (let i = 0, il = e.set.length; i < il; ++i) {
+                        _indices[i] = unit.elements[e.set[i]]
+                    }
+                    indices = SortedArray.ofSortedArray(_indices)
+                } else if (e.set.length === 0) {
+                    SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
+                    indices = SortedArray.ofSortedArray(_indices)
+                } else {
+                    const rangesSize = SortedRanges.size(e.ranges)
+                    SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
+                    SortedRanges.forEach(e.set, (v, i) => _indices[i + rangesSize] = unit.elements[v])
+                    indices = SortedArray.ofUnsortedArray(_indices) // requires sort
+                }
+
+                for (const unit of _units) {
+                    units.push(unit.getChild(indices))
+                }
+            }
+            return Structure.create(units, parent)
+        }
+
+        export function areEqual(a: Query, b: Query) {
+            if (a.elements.length !== b.elements.length) return false
+            for (let i = 0, il = a.elements.length; i < il; ++i) {
+                const elementA = a.elements[i]
+                const elementB = b.elements[i]
+                if (!SortedArray.areEqual(elementA.units, elementB.units)) return false
+                if (!SortedArray.areEqual(elementA.set, elementB.set)) return false
+                if (!SortedRanges.areEqual(elementA.ranges, elementB.ranges)) return false
+            }
+            return true
+        }
+    }
 }
 
 export default StructureElement