Skip to content
Snippets Groups Projects
Commit f52ee9db authored by David Sehnal's avatar David Sehnal
Browse files

Structure and selection cosntruction updates

parent 59f5f0ca
No related branches found
No related tags found
No related merge requests found
...@@ -16,6 +16,11 @@ describe('sortedArray', () => { ...@@ -16,6 +16,11 @@ describe('sortedArray', () => {
it(name, () => expect(a).toEqual(b)); 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 a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]); const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
...@@ -48,6 +53,12 @@ describe('sortedArray', () => { ...@@ -48,6 +53,12 @@ describe('sortedArray', () => {
testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1)); 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(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))) // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
}); });
\ No newline at end of file
...@@ -273,6 +273,22 @@ export function subtract(a: Nums, b: Nums) { ...@@ -273,6 +273,22 @@ export function subtract(a: Nums, b: Nums) {
return ofSortedArray(indices); 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 }; 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 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
function getSuitableIntersectionRange(a: Nums, b: Nums) { function getSuitableIntersectionRange(a: Nums, b: Nums) {
......
...@@ -40,6 +40,8 @@ namespace SortedArray { ...@@ -40,6 +40,8 @@ namespace SortedArray {
export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any; 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 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 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' } interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' }
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
import Query from './query' import Query from './query'
import Selection from './selection' import Selection from './selection'
import P from './properties' import P from './properties'
import { Structure, Element, Unit } from '../structure' import { Element, Unit } from '../structure'
import { OrderedSet, Segmentation } from 'mol-data/int' import { OrderedSet, Segmentation } from 'mol-data/int'
import { LinearGroupingBuilder } from './utils/builders';
export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s); export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s);
...@@ -113,55 +114,6 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A ...@@ -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 { function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query.Provider {
return async (structure, ctx) => { return async (structure, ctx) => {
const { units } = structure; const { units } = structure;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import { HashSet } from 'mol-data/generic' import { HashSet } from 'mol-data/generic'
import { Structure } from '../structure' 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 // A selection is a pair of a Structure and a sequence of unique AtomSets
type Selection = Selection.Singletons | Selection.Sequence type Selection = Selection.Singletons | Selection.Sequence
...@@ -31,7 +31,7 @@ namespace Selection { ...@@ -31,7 +31,7 @@ namespace Selection {
export function unionStructure(sel: Selection): Structure { export function unionStructure(sel: Selection): Structure {
if (isEmpty(sel)) return Structure.Empty; if (isEmpty(sel)) return Structure.Empty;
if (isSingleton(sel)) return sel.structure; if (isSingleton(sel)) return sel.structure;
return StructureUtils.union(sel.source, sel.structures); return structureUnion(sel.source, sel.structures);
} }
export interface Builder { export interface Builder {
...@@ -42,7 +42,7 @@ namespace Selection { ...@@ -42,7 +42,7 @@ namespace Selection {
function getSelection(source: Structure, structures: Structure[], allSingletons: boolean) { function getSelection(source: Structure, structures: Structure[], allSingletons: boolean) {
const len = structures.length; const len = structures.length;
if (len === 0) return Empty(source); 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); return Sequence(source, structures);
} }
......
/**
* 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
/**
* 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
/**
* 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
...@@ -181,7 +181,7 @@ namespace Structure { ...@@ -181,7 +181,7 @@ namespace Structure {
this.elementCount += elements.length; this.elementCount += elements.length;
} }
getStructure(): Structure { private _getStructure(deduplicateElements: boolean): Structure {
if (this.isEmpty) return Structure.Empty; if (this.isEmpty) return Structure.Empty;
const newUnits: Unit[] = []; const newUnits: Unit[] = [];
...@@ -193,7 +193,15 @@ namespace Structure { ...@@ -193,7 +193,15 @@ namespace Structure {
const id = this.ids[i]; const id = this.ids[i];
const parent = this.parent.unitMap.get(id); 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; const l = unit.length;
// if the length is the same, just copy the old unit. // if the length is the same, just copy the old unit.
...@@ -203,7 +211,7 @@ namespace Structure { ...@@ -203,7 +211,7 @@ namespace Structure {
continue; continue;
} }
if (!this.isSorted && l > 1) sortArray(unit); if (!this.isSorted && !sorted && l > 1) sortArray(unit);
let child = parent.getChild(SortedArray.ofSortedArray(unit)); let child = parent.getChild(SortedArray.ofSortedArray(unit));
const pivot = symmGroups.add(child.id, child); const pivot = symmGroups.add(child.id, child);
...@@ -214,6 +222,10 @@ namespace Structure { ...@@ -214,6 +222,10 @@ namespace Structure {
return create(newUnits); return create(newUnits);
} }
getStructure(deduplicateElements = false) {
return this._getStructure(deduplicateElements);
}
setSingletonLocation(location: Element.Location) { setSingletonLocation(location: Element.Location) {
const id = this.ids[0]; const id = this.ids[0];
location.unit = this.parent.unitMap.get(id); location.unit = this.parent.unitMap.get(id);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment