diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts index baa6bef0ea643ab3d72601cf5240dc84caa9682e..5c48cb5b8407a2841a505477667c8a12852a6a5a 100644 --- a/src/mol-data/int/impl/ordered-set.ts +++ b/src/mol-data/int/impl/ordered-set.ts @@ -132,8 +132,8 @@ function unionII(a: I, b: I) { if (I.areEqual(a, b)) return a; const sizeA = I.size(a), sizeB = I.size(b); - if (!sizeA) return b; if (!sizeB) return a; + if (!sizeA) return b; const minA = I.min(a), minB = I.min(b); if (areRangesIntersecting(a, b)) return I.ofRange(Math.min(minA, minB), Math.max(I.max(a), I.max(b))); let lSize, lMin, rSize, rMin; diff --git a/src/mol-model/structure/_spec/atom-set.1.spec.ts b/src/mol-model/structure/_spec/atom-set.1.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..917d69bf59ff94fd416e580d9ae8aa690400eb5c --- /dev/null +++ b/src/mol-model/structure/_spec/atom-set.1.spec.ts @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { OrderedSet } from 'mol-data/int' +import AtomSet from '../structure/atom/set.1' +import Atom from '../structure/atom' +import AtomGroup from '../structure/atom/group' + +describe('atom set', () => { + const p = (i: number, j: number) => Atom.create(i, j); + + function setToPairs(set: AtomSet): ArrayLike<Atom> { + const ret: Atom[] = []; + const it = AtomSet.atoms(set); + while (it.hasNext) { + ret[ret.length] = it.move(); + } + return ret; + } + + it('singleton pair', () => { + const set = AtomSet.ofAtoms([p(10, 11)], AtomSet.Empty); + expect(setToPairs(set)).toEqual([p(10, 11)]); + expect(AtomSet.atomHas(set, p(10, 11))).toBe(true); + expect(AtomSet.atomHas(set, p(11, 11))).toBe(false); + expect(AtomSet.atomGetAt(set, 0)).toBe(p(10, 11)); + expect(AtomSet.atomCount(set)).toBe(1); + }); + + it('multi', () => { + const gen = AtomSet.Generator(); + gen.add(1, AtomGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7]))); + gen.add(3, AtomGroup.createNew(OrderedSet.ofRange(0, 1))); + const set = gen.getSet(); + const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]; + expect(AtomSet.atomCount(set)).toBe(ret.length); + expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]); + expect(AtomSet.atomHas(set, p(10, 11))).toBe(false); + expect(AtomSet.atomHas(set, p(3, 0))).toBe(true); + expect(AtomSet.atomHas(set, p(1, 7))).toBe(true); + for (let i = 0; i < AtomSet.atomCount(set); i++) { + expect(Atom.areEqual(AtomSet.atomGetAt(set, i), ret[i])).toBe(true); + } + }); + + it('template', () => { + const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty) + const gen = AtomSet.TemplateGenerator(template); + gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6]))); + gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(3))); + const set = gen.getSet(); + + expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0)); + expect(AtomSet.unitGetById(set, 1)).toBe(AtomSet.unitGetById(template, 1)); + expect(set).toBe(template); + }); + + it('template 1', () => { + const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty) + const gen = AtomSet.TemplateGenerator(template); + gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6]))); + gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(4))); + const set = gen.getSet(); + + expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0)); + expect(AtomSet.unitGetById(set, 1) === AtomSet.unitGetById(template, 1)).toBe(false); + expect(set === template).toBe(false); + }); + + it('template union', () => { + const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty) + + const p13 = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const p01 = AtomSet.ofAtoms([p(0, 1)], AtomSet.Empty); + const p02 = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty); + const p06 = AtomSet.ofAtoms([p(0, 6)], AtomSet.Empty); + + const u0 = AtomSet.union([p01, p02, p06], template); + const u1 = AtomSet.union([p01, p02, p06, p13], template); + expect(AtomSet.unitGetById(u0, 0)).toBe(AtomSet.unitGetById(template, 0)); + expect(AtomSet.unitGetById(u1, 0)).toBe(AtomSet.unitGetById(template, 0)); + expect(AtomSet.unitGetById(u1, 1)).toBe(AtomSet.unitGetById(template, 1)); + expect(u1).toBe(template); + }); + + it('element at / index of', () => { + const control: Atom[] = []; + const gen = AtomSet.Generator(); + for (let i = 1; i < 10; i++) { + const set = []; + for (let j = 1; j < 7; j++) { + control[control.length] = p(i * i, j * j + 1); + set[set.length] = j * j + 1; + } + gen.add(i * i, AtomGroup.createNew(OrderedSet.ofSortedArray(set))); + } + const ms = gen.getSet(); + for (let i = 0; i < control.length; i++) { + expect(Atom.areEqual(AtomSet.atomGetAt(ms, i), control[i])).toBe(true); + } + + for (let i = 0; i < control.length; i++) { + expect(AtomSet.atomIndexOf(ms, control[i])).toBe(i); + } + }); + + it('packed pairs', () => { + const set = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]); + }); + + it('equality', () => { + const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty); + const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty); + + expect(AtomSet.areEqual(a, a)).toBe(true); + expect(AtomSet.areEqual(a, b)).toBe(true); + expect(AtomSet.areEqual(a, c)).toBe(false); + expect(AtomSet.areEqual(a, d)).toBe(false); + expect(AtomSet.areEqual(d, d)).toBe(true); + expect(AtomSet.areEqual(d, e)).toBe(true); + expect(AtomSet.areEqual(d, f)).toBe(false); + }); + + it('are intersecting', () => { + const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty); + const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty); + const g = AtomSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], AtomSet.Empty); + + expect(AtomSet.areIntersecting(a, a)).toBe(true); + expect(AtomSet.areIntersecting(a, b)).toBe(true); + expect(AtomSet.areIntersecting(a, c)).toBe(true); + expect(AtomSet.areIntersecting(a, d)).toBe(true); + expect(AtomSet.areIntersecting(a, g)).toBe(false); + expect(AtomSet.areIntersecting(d, d)).toBe(true); + expect(AtomSet.areIntersecting(d, e)).toBe(true); + expect(AtomSet.areIntersecting(d, f)).toBe(false); + }); + + it('intersection', () => { + const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty); + const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty); + expect(AtomSet.intersect(a, a)).toBe(a); + expect(setToPairs(AtomSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]); + expect(setToPairs(AtomSet.intersect(a, c))).toEqual([p(1, 3)]); + expect(setToPairs(AtomSet.intersect(c, d))).toEqual([]); + }); + + it('subtract', () => { + const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty); + const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty); + const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty); + const e = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty); + expect(setToPairs(AtomSet.subtract(a, a))).toEqual([]); + expect(setToPairs(AtomSet.subtract(a, a1))).toEqual([]); + expect(setToPairs(AtomSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]); + expect(setToPairs(AtomSet.subtract(c, d))).toEqual([p(1, 3)]); + expect(setToPairs(AtomSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]); + expect(setToPairs(AtomSet.subtract(c, a))).toEqual([]); + expect(setToPairs(AtomSet.subtract(d, a))).toEqual([p(2, 3)]); + expect(setToPairs(AtomSet.subtract(a, e))).toEqual([p(0, 1), p(0, 6), p(1, 3)]); + }); + + it('union', () => { + const a = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty); + const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty); + const b = AtomSet.ofAtoms([p(10, 3), p(0, 1)], AtomSet.Empty); + const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty); + const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty); + expect(AtomSet.union([a], AtomSet.Empty)).toBe(a); + expect(AtomSet.union([a, a], AtomSet.Empty)).toBe(a); + expect(setToPairs(AtomSet.union([a, a], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]); + expect(setToPairs(AtomSet.union([a, a1], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]); + expect(setToPairs(AtomSet.union([a, b], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]); + expect(setToPairs(AtomSet.union([c, d], AtomSet.Empty))).toEqual([p(1, 3), p(2, 3)]); + expect(setToPairs(AtomSet.union([a, b, c, d], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]); + }); +}); \ No newline at end of file diff --git a/src/mol-model/structure/structure/atom/impl/builder.ts b/src/mol-model/structure/structure/atom/impl/builder.ts index 798283debda5fb92b0de7978f8f90f38059123cf..cf9d2b8be70a5429c293b226da4eb723499e7a7a 100644 --- a/src/mol-model/structure/structure/atom/impl/builder.ts +++ b/src/mol-model/structure/structure/atom/impl/builder.ts @@ -45,7 +45,7 @@ export class Builder { const l = unit.length; if (!this.sorted && l > 1) sortArray(unit); - const set = l === 1 ? OrderedSet.ofSingleton(unit[0]) : OrderedSet.ofSortedArray(unit); + const set = OrderedSet.ofSortedArray(unit); const parentSet = AtomSet.unitGetById(this.parent, k); if (OrderedSet.areEqual(set, parentSet)) { generator.add(k, parentSet); diff --git a/src/mol-model/structure/structure/atom/impl/set.1.ts b/src/mol-model/structure/structure/atom/impl/set.1.ts index 86b48af759e7d0a738c74f80e32400aa91d40e6e..34ec544afa3b1005a02b0cbeb592cdacf5eadd5d 100644 --- a/src/mol-model/structure/structure/atom/impl/set.1.ts +++ b/src/mol-model/structure/structure/atom/impl/set.1.ts @@ -218,18 +218,17 @@ export class TemplateAtomSetGenerator { add(unit: number, group: AtomGroup) { if (AtomGroup.size(group) === 0) return; this.keys[this.keys.length] = unit; - const templ = this.templateGroups.get(unit); - if (AtomGroup.areEqual(templ, group)) { - this.groups.set(unit, templ); + let t: AtomGroup; + if (this.templateGroups.has(unit) && AtomGroup.areEqual(t = this.templateGroups.get(unit), group)) { + this.groups.set(unit, t); this.equalGroups++; } else { this.groups.set(unit, group); } - } getSet(): AtomSetImpl { - if (this.equalGroups === this.template.keys.length) { + if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) { return this.template; } return create(this.keys, this.groups); @@ -401,16 +400,15 @@ function findUnion(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) { function normalizeUnion(keys: number[], groups: IntMap.Mutable<AtomGroup>, template: AtomSetImpl) { let equalCount = 0; - let tg = template.groups; + let tg = template.groups, a: AtomGroup, t: AtomGroup; for (let i = 0, _i = keys.length; i < _i; i++) { const k = keys[i]; - const a = groups.get(k), t = tg.get(k); - if (AtomGroup.areEqual(a, t)) { + if (tg.has(k) && AtomGroup.areEqual(a = groups.get(k), t = tg.get(k))) { groups.set(k, t); equalCount++; } } - return equalCount === template.keys.length ? template : create(keys, groups); + return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups); } function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSetImpl) { @@ -421,6 +419,7 @@ function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSet if (groups.has(k)) { groups.set(k, AtomGroup.union(aG.get(k), groups.get(k))) } else { + keys[keys.length] = k; groups.set(k, aG.get(k)); } } diff --git a/src/mol-model/structure/structure/atom/set.1.ts b/src/mol-model/structure/structure/atom/set.1.ts new file mode 100644 index 0000000000000000000000000000000000000000..16431f544f25461ae555b1d3619e67c8adf5e48b --- /dev/null +++ b/src/mol-model/structure/structure/atom/set.1.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { SortedArray, Iterator } from 'mol-data/int' +import Atom from '../atom' +import AtomGroup from './group' +import * as Impl from './impl/set.1' +import * as Builders from './impl/builder' + +/** A map-like representation of grouped atom set */ +namespace AtomSet { + export const Empty: AtomSet = Impl.Empty as any; + + export const ofAtoms: (atoms: ArrayLike<Atom>, template: AtomSet) => AtomSet = Impl.ofAtoms as any; + + export const unitCount: (set: AtomSet) => number = Impl.keyCount as any; + export const unitIds: (set: AtomSet) => SortedArray = Impl.getKeys as any; + export const unitHas: (set: AtomSet, id: number) => boolean = Impl.hasKey as any; + export const unitGetId: (set: AtomSet, i: number) => number = Impl.getKey as any; + + export const unitGetById: (set: AtomSet, key: number) => AtomGroup = Impl.getByKey as any; + export const unitGetByIndex: (set: AtomSet, i: number) => AtomGroup = Impl.getByIndex as any; + + export const atomCount: (set: AtomSet) => number = Impl.size as any; + export const atomHas: (set: AtomSet, x: Atom) => boolean = Impl.hasAtom as any; + export const atomIndexOf: (set: AtomSet, x: Atom) => number = Impl.indexOf as any; + export const atomGetAt: (set: AtomSet, i: number) => Atom = Impl.getAt as any; + export const atoms: (set: AtomSet) => Iterator<Atom> = Impl.values as any; + + export const hashCode: (set: AtomSet) => number = Impl.hashCode as any; + export const areEqual: (a: AtomSet, b: AtomSet) => boolean = Impl.areEqual as any; + export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = Impl.areIntersecting as any; + + export const union: (sets: AtomSet[], template: AtomSet) => AtomSet = Impl.unionMany as any; + export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Impl.intersect as any; + export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Impl.subtract as any; + + export type Builder = Builders.Builder + export const LinearBuilder = Builders.LinearBuilder + export const UnsortedBuilder = Builders.UnsortedBuilder + + export interface Generator { add(unit: number, set: AtomGroup): void, getSet(): AtomSet } + export const Generator: () => Generator = Impl.Generator as any + export const TemplateGenerator: (template: AtomSet) => Generator = Impl.TemplateGenerator as any + + // TODO: bounding sphere + // TODO: distance, areWithIn? + // TODO: check connected + // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking? +} + +interface AtomSet { '@type': 'atom-set' | Atom['@type'] } + +export default AtomSet \ No newline at end of file