diff --git a/src/mol-data/util/hash-functions.ts b/src/mol-data/util/hash-functions.ts index 0f672694237f5d4c7c6a6d7c1df15527582ece27..c554ab9756992e90d40eb3e65330e823a691af53 100644 --- a/src/mol-data/util/hash-functions.ts +++ b/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]; diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index d1e7d090a5c5c67933bb2117aa94d6a60405c217..a7036691c294ae6c34c98523b5d6a9adb2cf2ed2 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/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 \ No newline at end of file