From f52ee9db2aa64c507c60e704e7557585805c13b0 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 14 May 2018 16:47:52 +0200 Subject: [PATCH] Structure and selection cosntruction updates --- src/mol-data/int/_spec/sorted-array.spec.ts | 11 ++ src/mol-data/int/impl/sorted-array.ts | 16 +++ src/mol-data/int/sorted-array.ts | 2 + src/mol-model/structure/query/generators.ts | 52 +-------- src/mol-model/structure/query/selection.ts | 6 +- src/mol-model/structure/query/utils.ts | 107 ------------------ .../structure/query/utils/builders.ts | 81 +++++++++++++ .../structure/query/utils/structure.ts | 103 +++++++++++++++++ .../structure/structure/structure.ts | 18 ++- 9 files changed, 233 insertions(+), 163 deletions(-) delete mode 100644 src/mol-model/structure/query/utils.ts create mode 100644 src/mol-model/structure/query/utils/builders.ts create mode 100644 src/mol-model/structure/query/utils/structure.ts diff --git a/src/mol-data/int/_spec/sorted-array.spec.ts b/src/mol-data/int/_spec/sorted-array.spec.ts index db8cd15d7..2cd82af32 100644 --- a/src/mol-data/int/_spec/sorted-array.spec.ts +++ b/src/mol-data/int/_spec/sorted-array.spec.ts @@ -16,6 +16,11 @@ describe('sortedArray', () => { it(name, () => expect(a).toEqual(b)); } + function compareArrays(a: ArrayLike<number>, b: ArrayLike<number>) { + expect(a.length).toBe(b.length); + for (let i = 0; i < a.length; i++) expect(a[i]).toBe(b[i]); + } + const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]); const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]); @@ -48,6 +53,12 @@ describe('sortedArray', () => { testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1)); + it('deduplicate', () => { + compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 1, 1])), [1]); + compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 2, 2, 3, 4])), [1, 2, 3, 4]); + compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]); + }); + // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3))) // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3))) }); \ No newline at end of file diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index 11a964b17..4a5476c16 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/src/mol-data/int/impl/sorted-array.ts @@ -273,6 +273,22 @@ export function subtract(a: Nums, b: Nums) { return ofSortedArray(indices); } +export function deduplicate(xs: Nums) { + if (xs.length < 2) return xs; + let count = 1; + for (let i = 0, _i = xs.length - 1; i < _i; i++) { + if (xs[i] !== xs[i + 1]) count++; + } + if (count === xs.length) return xs; + const ret = new Int32Array(count); + let o = 0; + for (let i = 0, _i = xs.length - 1; i < _i; i++) { + if (xs[i] !== xs[i + 1]) ret[o++] = xs[i]; + } + ret[o] = xs[xs.length - 1]; + return ret; +} + const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 }; // for small sets, just gets the whole range, for large sets does a bunch of binary searches function getSuitableIntersectionRange(a: Nums, b: Nums) { diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index 2b8391cb5..928e383dd 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/src/mol-data/int/sorted-array.ts @@ -40,6 +40,8 @@ namespace SortedArray { export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any; export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any; export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any; + + export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any; } interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' } diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts index 01445a1da..8bbc8a17d 100644 --- a/src/mol-model/structure/query/generators.ts +++ b/src/mol-model/structure/query/generators.ts @@ -7,8 +7,9 @@ import Query from './query' import Selection from './selection' import P from './properties' -import { Structure, Element, Unit } from '../structure' +import { Element, Unit } from '../structure' import { OrderedSet, Segmentation } from 'mol-data/int' +import { LinearGroupingBuilder } from './utils/builders'; export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s); @@ -113,55 +114,6 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A }; } -class LinearGroupingBuilder { - private builders: Structure.SubsetBuilder[] = []; - private builderMap = new Map<string, Structure.SubsetBuilder>(); - - add(key: any, unit: number, element: number) { - let b = this.builderMap.get(key); - if (!b) { - b = this.source.subsetBuilder(true); - this.builders[this.builders.length] = b; - this.builderMap.set(key, b); - } - b.addToUnit(unit, element); - } - - private allSingletons() { - for (let i = 0, _i = this.builders.length; i < _i; i++) { - if (this.builders[i].elementCount > 1) return false; - } - return true; - } - - private singletonSelection(): Selection { - const builder = this.source.subsetBuilder(true); - const loc = Element.Location(); - for (let i = 0, _i = this.builders.length; i < _i; i++) { - this.builders[i].setSingletonLocation(loc); - builder.addToUnit(loc.unit.id, loc.element); - } - return Selection.Singletons(this.source, builder.getStructure()); - } - - private fullSelection() { - const structures: Structure[] = new Array(this.builders.length); - for (let i = 0, _i = this.builders.length; i < _i; i++) { - structures[i] = this.builders[i].getStructure(); - } - return Selection.Sequence(this.source, structures); - } - - getSelection(): Selection { - const len = this.builders.length; - if (len === 0) return Selection.Empty(this.source); - if (this.allSingletons()) return this.singletonSelection(); - return this.fullSelection(); - } - - constructor(private source: Structure) { } -} - function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query.Provider { return async (structure, ctx) => { const { units } = structure; diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index ebe352b68..a1fc34f2a 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -6,7 +6,7 @@ import { HashSet } from 'mol-data/generic' import { Structure } from '../structure' -import { StructureUtils } from './utils'; +import { structureUnion } from './utils/structure'; // A selection is a pair of a Structure and a sequence of unique AtomSets type Selection = Selection.Singletons | Selection.Sequence @@ -31,7 +31,7 @@ namespace Selection { export function unionStructure(sel: Selection): Structure { if (isEmpty(sel)) return Structure.Empty; if (isSingleton(sel)) return sel.structure; - return StructureUtils.union(sel.source, sel.structures); + return structureUnion(sel.source, sel.structures); } export interface Builder { @@ -42,7 +42,7 @@ namespace Selection { function getSelection(source: Structure, structures: Structure[], allSingletons: boolean) { const len = structures.length; if (len === 0) return Empty(source); - if (allSingletons) return Singletons(source, StructureUtils.union(source, structures)); + if (allSingletons) return Singletons(source, structureUnion(source, structures)); return Sequence(source, structures); } diff --git a/src/mol-model/structure/query/utils.ts b/src/mol-model/structure/query/utils.ts deleted file mode 100644 index 2981878e9..000000000 --- a/src/mol-model/structure/query/utils.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { Structure, Unit } from '../structure' -import { SortedArray } from 'mol-data/int'; - -namespace StructureUtils { - export function union(source: Structure, structures: Structure[]) { - if (structures.length === 0) return Structure.Empty; - if (structures.length === 1) return structures[0]; - - const unitMap = new Map<number, SortedArray>(); - const fullUnits = new Set<number>(); - - for (const { units } of structures) { - for (let i = 0, _i = units.length; i < _i; i++) { - const u = units[i]; - if (unitMap.has(u.id)) { - // check if there is anything more to union in this particual unit. - if (fullUnits.has(u.id)) continue; - const merged = SortedArray.union(unitMap.get(u.id)!, u.elements); - unitMap.set(u.id, merged); - if (merged.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id); - } else { - unitMap.set(u.id, u.elements); - if (u.elements.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id); - } - } - } - - const builder = source.subsetBuilder(true); - unitMap.forEach(buildUnion, builder); - return builder.getStructure(); - } - - function buildUnion(this: Structure.SubsetBuilder, elements: SortedArray, id: number) { - this.setUnit(id, elements); - } - - export function areIntersecting(sA: Structure, sB: Structure): boolean { - if (sA === sB) return true; - - let a, b; - if (sA.units.length < sB.units.length) { a = sA; b = sB; } - else { a = sB; b = sA; } - - const aU = a.units, bU = b.unitMap; - - for (let i = 0, _i = aU.length; i < _i; i++) { - const u = aU[i]; - if (!bU.has(u.id)) continue; - const v = bU.get(u.id); - if (SortedArray.areIntersecting(u.elements, v.elements)) return true; - } - - return false; - } - - export function intersect(sA: Structure, sB: Structure): Structure { - if (sA === sB) return sA; - if (!areIntersecting(sA, sB)) return Structure.Empty; - - let a, b; - if (sA.units.length < sB.units.length) { a = sA; b = sB; } - else { a = sB; b = sA; } - - const aU = a.units, bU = b.unitMap; - const units: Unit[] = []; - - for (let i = 0, _i = aU.length; i < _i; i++) { - const u = aU[i]; - if (!bU.has(u.id)) continue; - const v = bU.get(u.id); - if (SortedArray.areIntersecting(u.elements, v.elements)) { - const int = SortedArray.intersect(u.elements, v.elements); - units[units.length] = u.getChild(int); - } - } - - return Structure.create(units); - } - - export function subtract(a: Structure, b: Structure): Structure { - if (a === b) return Structure.Empty; - if (!areIntersecting(a, b)) return a; - - const aU = a.units, bU = b.unitMap; - const units: Unit[] = []; - - for (let i = 0, _i = aU.length; i < _i; i++) { - const u = aU[i]; - if (!bU.has(u.id)) continue; - const v = bU.get(u.id); - const sub = SortedArray.intersect(u.elements, v.elements); - if (sub.length > 0) { - units[units.length] = u.getChild(sub); - } - } - - return Structure.create(units); - } -} - -export { StructureUtils } \ No newline at end of file diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts new file mode 100644 index 000000000..fa9020984 --- /dev/null +++ b/src/mol-model/structure/query/utils/builders.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Element, Structure } from '../../structure'; +import Selection from '../selection'; +import { HashSet } from 'mol-data/generic'; +import { structureUnion } from './structure'; + +export class UniqueStructuresBuilder { + private set = HashSet(Structure.hashCode, Structure.areEqual); + private structures: Structure[] = []; + private allSingletons = true; + + add(s: Structure) { + if (!s.elementCount) return; + if (s.elementCount !== 1) this.allSingletons = false; + if (this.set.add(s)) { + this.structures[this.structures.length] = s; + } + } + + getSelection() { + if (this.allSingletons) return Selection.Singletons(this.source, structureUnion(this.source, this.structures)); + return Selection.Sequence(this.source, this.structures); + } + + constructor(private source: Structure) { + } +} + +export class LinearGroupingBuilder { + private builders: Structure.SubsetBuilder[] = []; + private builderMap = new Map<string, Structure.SubsetBuilder>(); + + add(key: any, unit: number, element: number) { + let b = this.builderMap.get(key); + if (!b) { + b = this.source.subsetBuilder(true); + this.builders[this.builders.length] = b; + this.builderMap.set(key, b); + } + b.addToUnit(unit, element); + } + + private allSingletons() { + for (let i = 0, _i = this.builders.length; i < _i; i++) { + if (this.builders[i].elementCount > 1) return false; + } + return true; + } + + private singletonSelection(): Selection { + const builder = this.source.subsetBuilder(true); + const loc = Element.Location(); + for (let i = 0, _i = this.builders.length; i < _i; i++) { + this.builders[i].setSingletonLocation(loc); + builder.addToUnit(loc.unit.id, loc.element); + } + return Selection.Singletons(this.source, builder.getStructure()); + } + + private fullSelection() { + const structures: Structure[] = new Array(this.builders.length); + for (let i = 0, _i = this.builders.length; i < _i; i++) { + structures[i] = this.builders[i].getStructure(); + } + return Selection.Sequence(this.source, structures); + } + + getSelection(): Selection { + const len = this.builders.length; + if (len === 0) return Selection.Empty(this.source); + if (this.allSingletons()) return this.singletonSelection(); + return this.fullSelection(); + } + + constructor(private source: Structure) { } +} \ No newline at end of file diff --git a/src/mol-model/structure/query/utils/structure.ts b/src/mol-model/structure/query/utils/structure.ts new file mode 100644 index 000000000..903a5d0cb --- /dev/null +++ b/src/mol-model/structure/query/utils/structure.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Structure, Unit } from '../../structure' +import { SortedArray } from 'mol-data/int'; + +export function structureUnion(source: Structure, structures: Structure[]) { + if (structures.length === 0) return Structure.Empty; + if (structures.length === 1) return structures[0]; + + const unitMap = new Map<number, SortedArray>(); + const fullUnits = new Set<number>(); + + for (const { units } of structures) { + for (let i = 0, _i = units.length; i < _i; i++) { + const u = units[i]; + if (unitMap.has(u.id)) { + // check if there is anything more to union in this particual unit. + if (fullUnits.has(u.id)) continue; + const merged = SortedArray.union(unitMap.get(u.id)!, u.elements); + unitMap.set(u.id, merged); + if (merged.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id); + } else { + unitMap.set(u.id, u.elements); + if (u.elements.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id); + } + } + } + + const builder = source.subsetBuilder(true); + unitMap.forEach(buildUnion, builder); + return builder.getStructure(); +} + +function buildUnion(this: Structure.SubsetBuilder, elements: SortedArray, id: number) { + this.setUnit(id, elements); +} + +export function structureAreIntersecting(sA: Structure, sB: Structure): boolean { + if (sA === sB) return true; + + let a, b; + if (sA.units.length < sB.units.length) { a = sA; b = sB; } + else { a = sB; b = sA; } + + const aU = a.units, bU = b.unitMap; + + for (let i = 0, _i = aU.length; i < _i; i++) { + const u = aU[i]; + if (!bU.has(u.id)) continue; + const v = bU.get(u.id); + if (SortedArray.areIntersecting(u.elements, v.elements)) return true; + } + + return false; +} + +export function structureIntersect(sA: Structure, sB: Structure): Structure { + if (sA === sB) return sA; + if (!structureAreIntersecting(sA, sB)) return Structure.Empty; + + let a, b; + if (sA.units.length < sB.units.length) { a = sA; b = sB; } + else { a = sB; b = sA; } + + const aU = a.units, bU = b.unitMap; + const units: Unit[] = []; + + for (let i = 0, _i = aU.length; i < _i; i++) { + const u = aU[i]; + if (!bU.has(u.id)) continue; + const v = bU.get(u.id); + if (SortedArray.areIntersecting(u.elements, v.elements)) { + const int = SortedArray.intersect(u.elements, v.elements); + units[units.length] = u.getChild(int); + } + } + + return Structure.create(units); +} + +export function structureSubtract(a: Structure, b: Structure): Structure { + if (a === b) return Structure.Empty; + if (!structureAreIntersecting(a, b)) return a; + + const aU = a.units, bU = b.unitMap; + const units: Unit[] = []; + + for (let i = 0, _i = aU.length; i < _i; i++) { + const u = aU[i]; + if (!bU.has(u.id)) continue; + const v = bU.get(u.id); + const sub = SortedArray.intersect(u.elements, v.elements); + if (sub.length > 0) { + units[units.length] = u.getChild(sub); + } + } + + return Structure.create(units); +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 93d7d4b14..c0b39c2fa 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -181,7 +181,7 @@ namespace Structure { this.elementCount += elements.length; } - getStructure(): Structure { + private _getStructure(deduplicateElements: boolean): Structure { if (this.isEmpty) return Structure.Empty; const newUnits: Unit[] = []; @@ -193,7 +193,15 @@ namespace Structure { const id = this.ids[i]; const parent = this.parent.unitMap.get(id); - const unit = this.unitMap.get(id); + let unit: ArrayLike<number> = this.unitMap.get(id); + let sorted = false; + + if (deduplicateElements) { + if (!this.isSorted) sortArray(unit); + unit = SortedArray.deduplicate(SortedArray.ofSortedArray(this.currentUnit)); + sorted = true; + } + const l = unit.length; // if the length is the same, just copy the old unit. @@ -203,7 +211,7 @@ namespace Structure { continue; } - if (!this.isSorted && l > 1) sortArray(unit); + if (!this.isSorted && !sorted && l > 1) sortArray(unit); let child = parent.getChild(SortedArray.ofSortedArray(unit)); const pivot = symmGroups.add(child.id, child); @@ -214,6 +222,10 @@ namespace Structure { return create(newUnits); } + getStructure(deduplicateElements = false) { + return this._getStructure(deduplicateElements); + } + setSingletonLocation(location: Element.Location) { const id = this.ids[0]; location.unit = this.parent.unitMap.get(id); -- GitLab