diff --git a/src/mol-data/_spec/equiv-index.spec.ts b/src/mol-data/_spec/equiv-index.spec.ts index 9083c0145d0258be5815f9bc0c6f49c9effa5062..339022a969f1e072aeaa0199be98926106332623 100644 --- a/src/mol-data/_spec/equiv-index.spec.ts +++ b/src/mol-data/_spec/equiv-index.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import EquivalenceClasses from '../util/equivalence-classes' +import { EquivalenceClasses } from '../util' describe('equiv-classes', () => { it('integer mod classes', () => { diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index 54fe666de2589facbd59bc5107c4063a3d8cc194..adcd28cc2b69648eb4481a5478534052bf6b27b7 100644 --- a/src/mol-data/db/column.ts +++ b/src/mol-data/db/column.ts @@ -80,6 +80,10 @@ namespace Column { valueKind?: (row: number) => ValueKind, } + export function is(v: any): v is Column<any> { + return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value; + } + export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 } export function Undefined<T extends Schema>(rowCount: number, schema: T): Column<T['T']> { @@ -306,6 +310,7 @@ function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCt } function areColumnsEqual(a: Column<any>, b: Column<any>) { + if (a === b) return true; if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a.schema.valueType !== b.schema.valueType) return false; if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b); return areValuesEqual(a, b); diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts index d01629bf1650134e148ad469bcc31d37a4517cda..6335e37ed50eef0e761a90549ed53590e219647b 100644 --- a/src/mol-data/db/table.ts +++ b/src/mol-data/db/table.ts @@ -130,6 +130,26 @@ namespace Table { return true; } + + /** Allocate a new object with the given row values. */ + export function getRow<S extends Schema>(table: Table<S>, index: number) { + const row: Row<S> = Object.create(null); + const { _columns: cols } = table; + for (let i = 0; i < cols.length; i++) { + const c = cols[i]; + row[c] = table[c].value(index); + } + return row; + } + + export function getRows<S extends Schema>(table: Table<S>) { + const ret: Row<S>[] = []; + const { _rowCount: c } = table; + for (let i = 0; i < c; i++) { + ret[i] = getRow(table, i); + } + return ret; + } } export default Table \ No newline at end of file diff --git a/src/mol-data/int.ts b/src/mol-data/int.ts index 54c2795bbc6f772ebd25ee58501119ded48b35fe..24b005bd24adbc9949770670c2c6fb2a4535485b 100644 --- a/src/mol-data/int.ts +++ b/src/mol-data/int.ts @@ -10,6 +10,7 @@ import Segmentation from './int/segmentation' import SortedArray from './int/sorted-array' import Tuple from './int/tuple' import LinkedIndex from './int/linked-index' +import IntMap from './int/map' import Iterator from './iterator' -export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, Iterator } \ No newline at end of file +export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, IntMap, Iterator } \ No newline at end of file diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts index a5631f761af3bf18692ad13758ec304068da56c0..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; @@ -170,6 +170,7 @@ function intersectSI(a: S, b: I) { const start = I.start(r), end = I.end(r); const resultSize = end - start; if (!resultSize) return Empty; + if (resultSize === a.length) return a; const indices = new Int32Array(resultSize); let offset = 0; diff --git a/src/mol-data/int/map.ts b/src/mol-data/int/map.ts new file mode 100644 index 0000000000000000000000000000000000000000..30725a89c49238468e858c85cb88c65f3b3062e4 --- /dev/null +++ b/src/mol-data/int/map.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { iterableToArray } from '../util' + +// TODO: rename to "linear map" and just do key value mapping from index? + +/** Immutable by convention IntMap */ +interface IntMap<T> { + has(key: number): boolean, + keys(): IterableIterator<number>, + values(): IterableIterator<T>, + get(key: number): T, + readonly size: number +} + +namespace IntMap { + export const Empty: IntMap<any> = new Map<number, any>(); + + export interface Mutable<T> extends IntMap<T> { + set(key: number, value: T): void; + } + + export function keyArray<T>(map: IntMap<T>): number[] { + return iterableToArray(map.keys()); + } + + export function Mutable<T>(): Mutable<T> { + return new Map<number, T>() as Mutable<T>; + } + + export function asImmutable<T>(map: IntMap<T>): IntMap<T> { + return map; + } + + export function copy<T>(map: IntMap<T>): Mutable<T> { + const ret = Mutable<T>(); + const it = map.keys(); + while (true) { + const { done, value } = it.next(); + if (done) break; + ret.set(value, map.get(value)); + } + return ret; + } + + export function addFrom<T>(map: Mutable<T>, src: IntMap<T>) { + const it = src.keys(); + while (true) { + const { done, value } = it.next(); + if (done) break; + map.set(value, src.get(value)); + } + return map; + } +} + +export default IntMap; \ No newline at end of file diff --git a/src/mol-data/iterator.ts b/src/mol-data/iterator.ts index 106efa325b8c11f8d6640e1bbd135dbb18ff5ce8..8d5713f1fd56e138d61a6a1aba77010b746ae2bc 100644 --- a/src/mol-data/iterator.ts +++ b/src/mol-data/iterator.ts @@ -107,11 +107,11 @@ namespace Iterator { export function map<T, R>(base: Iterator<T>, f: (v: T) => R): Iterator<R> { return new MapIteratorImpl(base, f); } export function filter<T>(base: Iterator<T>, p: (v: T) => boolean): Iterator<T> { return new FilterIteratorImpl(base, p); } - // f can return non-undefined falsy value to stop the iteration. - export function forEach<T, Ctx>(it: Iterator<T>, f: (v: T, ctx: Ctx) => boolean | void, ctx: Ctx): Ctx { + // Iterate until first truthy value is returned. + export function forEach<T, Ctx>(it: Iterator<T>, f: (v: T, ctx: Ctx) => any, ctx: Ctx): Ctx { while (it.hasNext) { const c = f(it.move(), ctx); - if (typeof c !== 'undefined' && !c) return ctx; + if (c) return ctx; } return ctx; } diff --git a/src/mol-data/util.ts b/src/mol-data/util.ts index ab0c73fcfc7be65d2974c403b7fc010dc118b95a..629b3b517d6f1ad80151a734f369083529c00ba5 100644 --- a/src/mol-data/util.ts +++ b/src/mol-data/util.ts @@ -4,12 +4,11 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import ChunkedArray from './util/chunked-array' -import EquivalenceClasses from './util/chunked-array' -import HashSet from './util/hash-set' -import UniqueArray from './util/unique-array' - +export * from './util/chunked-array' +export * from './util/unique-array' +export * from './util/hash-set' +export * from './util/equivalence-classes' export * from './util/hash-functions' export * from './util/sort' - -export { ChunkedArray, EquivalenceClasses, HashSet, UniqueArray } \ No newline at end of file +export * from './util/grouping' +export * from './util/array' \ No newline at end of file diff --git a/src/mol-data/util/array.ts b/src/mol-data/util/array.ts new file mode 100644 index 0000000000000000000000000000000000000000..664138327b5b67fab885efd6e0ec035f6efaf895 --- /dev/null +++ b/src/mol-data/util/array.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * from https://github.com/dsehnal/CIFTools.js + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export function arrayFind<T>(array: ArrayLike<T>, f: (v: T) => boolean): T | undefined { + for (let i = 0, _i = array.length; i < _i; i++) { + if (f(array[i])) return array[i]; + } + return void 0; +} + +export function iterableToArray<T>(it: IterableIterator<T>): T[] { + if (Array.from) return Array.from(it); + + const ret = []; + while (true) { + const { done, value } = it.next(); + if (done) break; + ret[ret.length] = value; + } + return ret; +} \ No newline at end of file diff --git a/src/mol-data/util/chunked-array.ts b/src/mol-data/util/chunked-array.ts index 0664a4c12ce99348f609b0d8558e28836f0b8dc2..f9b977571f543a317d0c12a618f61442af8fd3c3 100644 --- a/src/mol-data/util/chunked-array.ts +++ b/src/mol-data/util/chunked-array.ts @@ -134,4 +134,4 @@ namespace ChunkedArray { } } -export default ChunkedArray \ No newline at end of file +export { ChunkedArray } \ No newline at end of file diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts index 936d39b299ed33956605642cd7b2c8a7041ef368..e40456499bcc6315d01d6ce17bca83ccd243323a 100644 --- a/src/mol-data/util/equivalence-classes.ts +++ b/src/mol-data/util/equivalence-classes.ts @@ -6,7 +6,7 @@ class EquivalenceClassesImpl<K, V> { private id = 0; - private byHash: { [hash: number]: { id: number, keys: K[], value: V }[] } = Object.create(null); + private byHash = new Map<number, { id: number, keys: K[], value: V }[]>(); readonly groups: K[][] = []; @@ -19,8 +19,8 @@ class EquivalenceClassesImpl<K, V> { add(key: K, a: V) { const hash = this.getHash(a); - if (!!this.byHash[hash]) { - const groups = this.byHash[hash]; + if (this.byHash.has(hash)) { + const groups = this.byHash.get(hash)!; for (let i = 0, _i = groups.length; i < _i; i++) { const group = groups[i]; if (this.areEqual(a, group.value)) { @@ -33,7 +33,7 @@ class EquivalenceClassesImpl<K, V> { return group.id; } else { const group = this.createGroup(key, a); - this.byHash[hash] = [group]; + this.byHash.set(hash, [group]); return group.id; } } @@ -41,8 +41,6 @@ class EquivalenceClassesImpl<K, V> { constructor(private getHash: (v: V) => any, private areEqual: (a: V, b: V) => boolean) { } } -function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) { +export function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) { return new EquivalenceClassesImpl<K, V>(getHash, areEqual); } - -export default EquivalenceClasses; \ No newline at end of file diff --git a/src/mol-data/util/grouping.ts b/src/mol-data/util/grouping.ts new file mode 100644 index 0000000000000000000000000000000000000000..71e9ce5a045ce14238ae3a1ffece9e08114a196b --- /dev/null +++ b/src/mol-data/util/grouping.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Column } from '../db' + +export interface Grouping<V, K> { + map: Map<K, V[]>, + keys: ReadonlyArray<K>, + groups: ReadonlyArray<ReadonlyArray<V>> +} + +class GroupingImpl<K, V> { + readonly map = new Map<K, V[]>(); + readonly keys: K[] = []; + readonly groups: V[][] = []; + + add(a: V) { + const key = this.getKey(a) as any; + if (!!this.map.has(key)) { + const group = this.map.get(key)!; + group[group.length] = a; + } else { + const group = [a]; + this.map.set(key, group); + this.keys[this.keys.length] = key; + this.groups[this.groups.length] = group; + } + } + + getGrouping(): Grouping<V, K> { + return { keys: this.keys, groups: this.groups, map: this.map }; + } + + constructor(private getKey: (v: V) => K) { } +} + +export function Grouper<V, K>(getKey: (x: V) => K) { + return new GroupingImpl<K, V>(getKey); +} + +export function groupBy<V, K>(values: ArrayLike<V> | Column<V>, getKey: (x: V) => K) { + const gs = Grouper(getKey); + if (Column.is(values)) { + const v = values.value; + for (let i = 0, _i = values.rowCount; i < _i; i++) gs.add(v(i)); + } else { + for (let i = 0, _i = values.length; i < _i; i++) gs.add(values[i]); + } + return gs.getGrouping(); +} \ No newline at end of file diff --git a/src/mol-data/util/hash-set.ts b/src/mol-data/util/hash-set.ts index 297b8eeb6b9c09c121739a7c9a468d3443a343dc..cbcf7560a225471662234ced71795cbbfbe56cc5 100644 --- a/src/mol-data/util/hash-set.ts +++ b/src/mol-data/util/hash-set.ts @@ -12,12 +12,12 @@ interface SetLike<T> { class HashSetImpl<T> implements SetLike<T> { size: number = 0; - private byHash: { [hash: number]: T[] } = Object.create(null); + private byHash = new Map<number, T[]>(); add(a: T) { const hash = this.getHash(a); - if (!!this.byHash[hash]) { - const xs = this.byHash[hash]; + if (this.byHash.has(hash)) { + const xs = this.byHash.get(hash)!; for (let i = 0, _i = xs.length; i < _i; i++) { if (this.areEqual(a, xs[i])) return false; } @@ -25,7 +25,7 @@ class HashSetImpl<T> implements SetLike<T> { this.size++; return true; } else { - this.byHash[hash] = [a]; + this.byHash.set(hash, [a]); this.size++; return true; } @@ -33,8 +33,8 @@ class HashSetImpl<T> implements SetLike<T> { has(v: T) { const hash = this.getHash(v); - if (!this.byHash[hash]) return false; - const xs = this.byHash[hash]; + if (!this.byHash.has(hash)) return false; + const xs = this.byHash.get(hash)!; for (let i = 0, _i = xs.length; i < _i; i++) { if (this.areEqual(v, xs[i])) return true; } @@ -45,8 +45,6 @@ class HashSetImpl<T> implements SetLike<T> { } // TODO: add implementations with multilevel hashing support? -function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> { +export function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> { return new HashSetImpl<T>(getHash, areEqual); -} - -export default HashSet; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-data/util/unique-array.ts b/src/mol-data/util/unique-array.ts index f628baa9601ff5d14c01a8af256b996fcf150786..5a5caa501af2ba0ba4098c9cf0904ee8d9296859 100644 --- a/src/mol-data/util/unique-array.ts +++ b/src/mol-data/util/unique-array.ts @@ -21,4 +21,4 @@ namespace UniqueArray { } } -export default UniqueArray \ No newline at end of file +export { UniqueArray } \ No newline at end of file diff --git a/src/mol-io/common/binary-cif/array-encoder.ts b/src/mol-io/common/binary-cif/array-encoder.ts index 344171b8a8a910f45d32d6c6d885ab9c46b6200f..67a9084ca0a578179bcf589a3e4a47907ed2e6ee 100644 --- a/src/mol-io/common/binary-cif/array-encoder.ts +++ b/src/mol-io/common/binary-cif/array-encoder.ts @@ -7,7 +7,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import ChunkedArray from 'mol-data/util/chunked-array' +import { ChunkedArray } from 'mol-data/util' import { Encoding, EncodedData } from './encoding' export interface ArrayEncoder { diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index e37380d02f0e6c7daea7eefdb8ebd1046f57c7ff..21fad348a834a7c2d6781974853105ac99757be2 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/src/mol-io/writer/cif/encoder.ts @@ -14,7 +14,11 @@ import Encoder from '../encoder' // TODO: add "repeat encoding"? [[1, 2], [1, 2], [1, 2]] --- Repeat ---> [[1, 2], 3] // TODO: Add "higher level fields"? (i.e. generalization of repeat) // TODO: Add tensor field definition -// TODO: align "data blocks" to 8 byte offsets +// TODO: align "data blocks" to 8 byte offsets for fast typed array windows? (prolly needs some testing if this is actually the case too) +// TODO: "parametric encoders"? Specify encoding as [{ param: 'value1', encoding1 }, { param: 'value2', encoding2 }] +// then the encoder can specify { param: 'value1' } and the correct encoding will be used. +// Use case: variable precision encoding for different fields. +// Perhaps implement this as parameter spaces... export const enum FieldType { Str, Int, Float diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 1e3a57de38f5a782220061e838ca5ee2dafcdc28..dcc83ade56b232b7fa5a0d208aff3d57b76aaeb8 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -21,15 +21,24 @@ namespace SymmetryOperator { export const DefaultName = '1_555' export const Default: SymmetryOperator = create(DefaultName, Mat4.identity()); - export function create(name: string, matrix: Mat4, hkl?: number[]): SymmetryOperator { - const _hkl = hkl ? Vec3.create(hkl[0], hkl[1], hkl[2]) : Vec3.zero(); + const RotationEpsilon = 0.0001; + + export function create(name: string, matrix: Mat4, hkl?: Vec3): SymmetryOperator { + const _hkl = hkl ? Vec3.copy(Vec3.zero(), hkl) : Vec3.zero(); if (Mat4.isIdentity(matrix)) return { name, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl }; - if (!Mat4.isRotationAndTranslation(matrix)) throw new Error('Symmetry operator must be a composition of rotation and translation.'); + if (!Mat4.isRotationAndTranslation(matrix, RotationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`); return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl }; } + // Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix) + export function compose(first: SymmetryOperator, second: SymmetryOperator) { + const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix); + return create(second.name, matrix, second.hkl); + } + export interface CoordinateMapper { (index: number, slot: Vec3): Vec3 } export interface ArrayMapping { + readonly operator: SymmetryOperator, readonly invariantPosition: CoordinateMapper, readonly position: CoordinateMapper, x(index: number): number, diff --git a/src/mol-math/linear-algebra/3d.ts b/src/mol-math/linear-algebra/3d.ts index f989a61c08071c119239843a33b756dea1cbd267..1609b99cf653e3f37380a6f5193cd26cd829551f 100644 --- a/src/mol-math/linear-algebra/3d.ts +++ b/src/mol-math/linear-algebra/3d.ts @@ -214,7 +214,7 @@ export namespace Mat4 { return mul(out, mul(out, a, b), c); } - export function translate(out: Mat4, a: Mat4, v: Mat4) { + export function translate(out: Mat4, a: Mat4, v: Vec3) { const x = v[0], y = v[1], z = v[2]; let a00: number, a01: number, a02: number, a03: number, a10: number, a11: number, a12: number, a13: number, @@ -243,7 +243,7 @@ export namespace Mat4 { return out; } - export function fromTranslation(out: Mat4, v: Mat4) { + export function fromTranslation(out: Mat4, v: Vec3) { out[0] = 1; out[1] = 0; out[2] = 0; @@ -263,6 +263,13 @@ export namespace Mat4 { return out; } + export function setTranslation(out: Mat4, v: Vec3) { + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + return out; + } + export function rotate(out: Mat4, a: Mat4, rad: number, axis: Mat4) { let x = axis[0], y = axis[1], z = axis[2], len = Math.sqrt(x * x + y * y + z * z), @@ -432,15 +439,28 @@ export namespace Mat4 { return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; } - export function isRotationAndTranslation(a: Mat4) { + /** + * Check if the matrix has the form + * [ Rotation Translation ] + * [ 0 1 ] + */ + export function isRotationAndTranslation(a: Mat4, eps?: number) { + return _isRotationAndTranslation(a, typeof eps !== 'undefined' ? eps : EPSILON.Value) + } + + function _isRotationAndTranslation(a: Mat4, eps: number) { const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], /* a30 = a[12], a31 = a[13], a32 = a[14],*/ a33 = a[15]; - if (a33 !== 1 || a03 !== 0 || a13 !== 0 || a23 !== 0) return false; - const det3x3 = a00 * (a11 * a22 - a21 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20); - if (det3x3 < 1 - EPSILON.Value || det3x3 > 1 + EPSILON.Value) return false; + if (a33 !== 1 || a03 !== 0 || a13 !== 0 || a23 !== 0) { + return false; + } + const det3x3 = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20); + if (det3x3 < 1 - eps || det3x3 > 1 + eps) { + return false; + } return true; } } diff --git a/src/mol-model/structure/_spec/atom-set.spec.ts b/src/mol-model/structure/_spec/atom-set.spec.ts index d77dd5e869d41acd27f28c0f32ecaeb70525816d..eb59d6c171c3ecbcd25c386309d0ee545acd2e07 100644 --- a/src/mol-model/structure/_spec/atom-set.spec.ts +++ b/src/mol-model/structure/_spec/atom-set.spec.ts @@ -7,6 +7,7 @@ import { OrderedSet } from 'mol-data/int' import AtomSet from '../structure/atom/set' import Atom from '../structure/atom' +import AtomGroup from '../structure/atom/group' describe('atom set', () => { const p = (i: number, j: number) => Atom.create(i, j); @@ -21,7 +22,7 @@ describe('atom set', () => { } it('singleton pair', () => { - const set = AtomSet.create(p(10, 11)); + 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); @@ -29,16 +30,20 @@ describe('atom set', () => { expect(AtomSet.atomCount(set)).toBe(1); }); - it('singleton number', () => { - const set = AtomSet.create(p(10, 11)); + it('singleton atom', () => { + const set = AtomSet.singleton(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 set = AtomSet.create({ - 1: OrderedSet.ofSortedArray([4, 6, 7]), - 3: OrderedSet.ofRange(0, 1), - }); + 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)]); @@ -50,18 +55,58 @@ describe('atom set', () => { } }); + 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, OrderedSet.ofSortedArray([1, 2, 6])); + gen.add(1, 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, OrderedSet.ofSortedArray([1, 2, 6])); + gen.add(1, 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 sets = Object.create(null); + 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; } - sets[i * i] = OrderedSet.ofSortedArray(set); + gen.add(i * i, AtomGroup.createNew(OrderedSet.ofSortedArray(set))); } - const ms = AtomSet.create(sets); + const ms = gen.getSet(); for (let i = 0; i < control.length; i++) { expect(Atom.areEqual(AtomSet.atomGetAt(ms, i), control[i])).toBe(true); } @@ -72,17 +117,17 @@ describe('atom set', () => { }); it('packed pairs', () => { - const set = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); + 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.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const b = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const c = AtomSet.create([p(1, 3), p(0, 4), p(0, 6), p(0, 2)]); - const d = AtomSet.create([p(1, 3)]); - const e = AtomSet.create([p(1, 3)]); - const f = AtomSet.create([p(3, 3)]); + 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); @@ -94,13 +139,13 @@ describe('atom set', () => { }); it('are intersecting', () => { - const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const b = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const c = AtomSet.create([p(1, 3), p(0, 4), p(0, 6), p(0, 2)]); - const d = AtomSet.create([p(1, 3)]); - const e = AtomSet.create([p(1, 3)]); - const f = AtomSet.create([p(3, 3)]); - const g = AtomSet.create([p(10, 3), p(8, 1), p(7, 6), p(3, 2)]); + 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); @@ -113,10 +158,10 @@ describe('atom set', () => { }); it('intersection', () => { - const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const b = AtomSet.create([p(10, 3), p(0, 1), p(0, 6), p(4, 2)]); - const c = AtomSet.create([p(1, 3)]); - const d = AtomSet.create([p(2, 3)]); + 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)]); @@ -124,12 +169,12 @@ describe('atom set', () => { }); it('subtract', () => { - const a = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const a1 = AtomSet.create([p(1, 3), p(0, 1), p(0, 6), p(0, 2)]); - const b = AtomSet.create([p(10, 3), p(0, 1), p(0, 6), p(4, 2)]); - const c = AtomSet.create([p(1, 3)]); - const d = AtomSet.create([p(2, 3)]); - const e = AtomSet.create([p(0, 2)]); + 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)]); @@ -141,17 +186,17 @@ describe('atom set', () => { }); it('union', () => { - const a = AtomSet.create([p(1, 3), p(0, 1)]); - const a1 = AtomSet.create([p(1, 3), p(0, 1)]); - const b = AtomSet.create([p(10, 3), p(0, 1)]); - const c = AtomSet.create([p(1, 3)]); - const d = AtomSet.create([p(2, 3)]); - expect(AtomSet.unionMany([a])).toBe(a); - expect(AtomSet.union(a, a)).toBe(a); - expect(setToPairs(AtomSet.union(a, a))).toEqual([p(0, 1), p(1, 3)]); - expect(setToPairs(AtomSet.union(a, a1))).toEqual([p(0, 1), p(1, 3)]); - expect(setToPairs(AtomSet.union(a, b))).toEqual([p(0, 1), p(1, 3), p(10, 3)]); - expect(setToPairs(AtomSet.union(c, d))).toEqual([p(1, 3), p(2, 3)]); - expect(setToPairs(AtomSet.unionMany([a, b, c, d]))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]); + 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/model.ts b/src/mol-model/structure/model.ts index 0693de0dfa574bfd37b713d6f5e219b9811e49ba..c53fd0986639873923cf30fbceba2d6010233741 100644 --- a/src/mol-model/structure/model.ts +++ b/src/mol-model/structure/model.ts @@ -7,5 +7,6 @@ import Model from './model/model' import * as Types from './model/types' import Format from './model/format' +import ModelSymmetry from './model/properties/symmetry' -export { Model, Types, Format } \ No newline at end of file +export { Model, Types, Format, ModelSymmetry } \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index b0f421ca8420ff32f22abfeda7ee75ece663f503..db14e11e7c294931b725ae8dc1cff820ce130365 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -11,8 +11,10 @@ import Format from '../format' import Model from '../model' import * as Hierarchy from '../properties/hierarchy' import Conformation from '../properties/conformation' +import Symmetry from '../properties/symmetry' import findHierarchyKeys from '../utils/hierarchy-keys' import { ElementSymbol} from '../types' +import createAssemblies from './mmcif/assembly' import mmCIF_Format = Format.mmCIF @@ -76,6 +78,10 @@ function getConformation({ data }: mmCIF_Format, bounds: Interval): Conformation } } +function getSymmetry(format: mmCIF_Format): Symmetry { + return { assemblies: createAssemblies(format) }; +} + function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) { // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300 return Table.areEqual(a.chains as Table<Hierarchy.ChainsSchema>, b.chains as Table<Hierarchy.ChainsSchema>) @@ -105,6 +111,7 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)), hierarchy: { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments }, conformation: getConformation(format, bounds), + symmetry: getSymmetry(format), atomCount: Interval.size(bounds) }; } diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts new file mode 100644 index 0000000000000000000000000000000000000000..35bbb7d650f398f371d905890c1ffbbb5fce55fa --- /dev/null +++ b/src/mol-model/structure/model/formats/mmcif/assembly.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Mat4, Tensor } from 'mol-math/linear-algebra' +import SymmetryOperator from 'mol-math/geometry/symmetry-operator' +import Format from '../../format' +import { Assembly, OperatorGroup, OperatorGroups } from '../../properties/symmetry' +import { Queries as Q } from '../../../query' + +import mmCIF_Format = Format.mmCIF + +export default function create(format: mmCIF_Format): ReadonlyArray<Assembly> { + const { pdbx_struct_assembly } = format.data; + if (!pdbx_struct_assembly._rowCount) return []; + + const matrices = getMatrices(format); + const assemblies: Assembly[] = []; + for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) { + assemblies[assemblies.length] = createAssembly(format, i, matrices); + } + return assemblies; +} + +type Matrices = Map<string, Mat4> +type Generator = { expression: string, asymIds: string[] } + +function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices): Assembly { + const { pdbx_struct_assembly, pdbx_struct_assembly_gen } = format.data; + + const id = pdbx_struct_assembly.id.value(index); + const details = pdbx_struct_assembly.details.value(index); + const generators: Generator[] = []; + + const { assembly_id, oper_expression, asym_id_list } = pdbx_struct_assembly_gen; + + for (let i = 0, _i = pdbx_struct_assembly_gen._rowCount; i < _i; i++) { + if (assembly_id.value(i) !== id) continue; + generators[generators.length] = { + expression: oper_expression.value(i), + asymIds: asym_id_list.value(i).split(',').map(x => x.trim()).filter(x => !!x) + }; + } + + return Assembly.create(id, details, operatorGroupsProvider(generators, matrices)); +} + +function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () => OperatorGroups { + return () => { + const groups: OperatorGroup[] = []; + + let operatorOffset = 0; + for (let i = 0; i < generators.length; i++) { + const gen = generators[i]; + const operatorList = parseOperatorList(gen.expression); + const operatorNames = expandOperators(operatorList); + const operators = getAssemblyOperators(matrices, operatorNames, operatorOffset); + const selector = Q.generators.atoms({ chainTest: Q.pred.and( + Q.pred.eq(Q.props.unit.operator_name, SymmetryOperator.DefaultName), + Q.pred.inSet(Q.props.chain.label_asym_id, gen.asymIds) + )}); + groups[groups.length] = { selector, operators }; + operatorOffset += operators.length; + } + + return groups; + } +} + +function getMatrices({ data }: mmCIF_Format): Matrices { + const { pdbx_struct_oper_list } = data; + const { id, matrix, vector, _schema } = pdbx_struct_oper_list; + const matrices = new Map<string, Mat4>(); + + for (let i = 0, _i = pdbx_struct_oper_list._rowCount; i < _i; i++) { + const m = Tensor.toMat4(_schema.matrix.space, matrix.value(i)); + const t = Tensor.toVec3(_schema.vector.space, vector.value(i)); + Mat4.setTranslation(m, t); + Mat4.setValue(m, 3, 3, 1); + matrices.set(id.value(i), m); + } + + return matrices; +} + +function expandOperators(operatorList: string[][]) { + const ops: string[][] = []; + const currentOp: string[] = []; + for (let i = 0; i < operatorList.length; i++) currentOp[i] = ''; + expandOperators1(operatorList, ops, operatorList.length - 1, currentOp); + return ops; +} + +function expandOperators1(operatorNames: string[][], list: string[][], i: number, current: string[]) { + if (i < 0) { + list[list.length] = current.slice(0); + return; + } + + let ops = operatorNames[i], len = ops.length; + for (let j = 0; j < len; j++) { + current[i] = ops[j]; + expandOperators1(operatorNames, list, i - 1, current); + } +} + +function getAssemblyOperators(matrices: Matrices, operatorNames: string[][], startIndex: number) { + const operators: SymmetryOperator[] = []; + + let index = startIndex; + for (let op of operatorNames) { + let m = Mat4.identity(); + for (let i = 0; i < op.length; i++) { + Mat4.mul(m, m, matrices.get(op[i])!); + } + index++; + operators[operators.length] = SymmetryOperator.create(`A-${index}`, m); + } + + return operators; +} + +function parseOperatorList(value: string): string[][] { + // '(X0)(1-5)' becomes [['X0'], ['1', '2', '3', '4', '5']] + // kudos to Glen van Ginkel. + + const oeRegex = /\(?([^\(\)]+)\)?]*/g, groups: string[] = [], ret: string[][] = []; + + let g: any; + while (g = oeRegex.exec(value)) groups[groups.length] = g[1]; + + groups.forEach(g => { + const group: string[] = []; + g.split(',').forEach(e => { + const dashIndex = e.indexOf('-'); + if (dashIndex > 0) { + const from = parseInt(e.substring(0, dashIndex)), to = parseInt(e.substr(dashIndex + 1)); + for (let i = from; i <= to; i++) group[group.length] = i.toString(); + } else { + group[group.length] = e.trim(); + } + }); + ret[ret.length] = group; + }); + + return ret; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index c7a33db549de634b695c23a9baadc7ab5a4842f9..0a24bb472615be0297e7388c35057132dc4d7ed4 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -6,8 +6,9 @@ import UUID from 'mol-util/uuid' import Format from './format' -import HierarchyProperties from './properties/hierarchy' -import ConformationProperties from './properties/conformation' +import Hierarchy from './properties/hierarchy' +import Conformation from './properties/conformation' +import Symmetry from './properties/symmetry' import from_mmCIF from './formats/mmcif' @@ -23,8 +24,9 @@ interface Model extends Readonly<{ sourceData: Format, - hierarchy: HierarchyProperties, - conformation: ConformationProperties, + hierarchy: Hierarchy, + conformation: Conformation, + symmetry: Symmetry, atomCount: number }> { } diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..dfe599c2a6aec7c4a32cc8c5499e0cb7112d8a18 --- /dev/null +++ b/src/mol-model/structure/model/properties/symmetry.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import SymmetryOperator from 'mol-math/geometry/symmetry-operator' +import { arrayFind } from 'mol-data/util' +import { Query } from '../../query' +import { Model } from '../../model' + +/** Determine an atom set and a list of operators that should be applied to that set */ +export interface OperatorGroup { + readonly selector: Query, + readonly operators: ReadonlyArray<SymmetryOperator> +} + +export type OperatorGroups = ReadonlyArray<OperatorGroup> + +export class Assembly { + readonly id: string; + readonly details: string; + + private _operators: OperatorGroups; + get operatorGroups(): OperatorGroups { + if (this._operators) return this._operators; + this._operators = this.operatorsProvider(); + return this._operators; + } + + constructor(id: string, details: string, private operatorsProvider: () => OperatorGroups) { + this.id = id; + this.details = details; + } +} + +export namespace Assembly { + export function create(id: string, details: string, operatorsProvider: () => OperatorGroups): Assembly { + return new Assembly(id, details, operatorsProvider); + } +} + +interface Symmetry { + readonly assemblies: ReadonlyArray<Assembly>, +} + +namespace Symmetry { + export const Empty: Symmetry = { assemblies: [] }; + + export function findAssembly(model: Model, id: string): Assembly | undefined { + const _id = id.toLocaleLowerCase(); + return arrayFind(model.symmetry.assemblies, a => a.id.toLowerCase() === _id); + } +} + +export default Symmetry \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/transforms.ts b/src/mol-model/structure/model/properties/transforms.ts deleted file mode 100644 index 42d9b7797614f887871b1196fd5816ceef643987..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/transforms.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO: symmetry and assebmlies descriptors - -//import Column from 'mol-base/collections/column' \ No newline at end of file diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts index 12d43abc4ef8c40f3b0f44735a3fbc4e58d9030c..31b3ff2c9e8b483bab95a8f0c3c4f56720032428 100644 --- a/src/mol-model/structure/query/generators.ts +++ b/src/mol-model/structure/query/generators.ts @@ -10,9 +10,9 @@ import P from './properties' import { Structure, AtomSet, Atom } from '../structure' import { OrderedSet, Segmentation } from 'mol-data/int' -export const all: Query = s => s; +export const all: Query = s => Selection.Singletons(s, s.atoms); -export interface AtomGroupsParams { +export interface AtomQueryParams { entityTest: Atom.Predicate, chainTest: Atom.Predicate, residueTest: Atom.Predicate, @@ -20,11 +20,18 @@ export interface AtomGroupsParams { groupBy: Atom.Property<any> } -export function atoms(params?: Partial<AtomGroupsParams>): Query { +export interface AtomGroupsQueryParams extends AtomQueryParams { + groupBy: Atom.Property<any> +} + +export function residues(params?: Partial<AtomQueryParams>) { return atoms({ ...params, groupBy: P.residue.key }); } +export function chains(params?: Partial<AtomQueryParams>) { return atoms({ ...params, groupBy: P.chain.key }); } + +export function atoms(params?: Partial<AtomGroupsQueryParams>): Query { if (!params || (!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy)) return all; if (!!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy) return atomGroupsLinear(params.atomTest); - const normalized: AtomGroupsParams = { + const normalized: AtomGroupsQueryParams = { entityTest: params.entityTest || P.constant.true, chainTest: params.chainTest || P.constant.true, residueTest: params.residueTest || P.constant.true, @@ -46,7 +53,7 @@ function atomGroupsLinear(atomTest: Atom.Predicate): Query { for (let i = 0, _i = unitIds.length; i < _i; i++) { const unitId = unitIds[i]; l.unit = units[unitId]; - const set = AtomSet.unitGetByIndex(atoms, i); + const set = AtomSet.unitGetByIndex(atoms, i).atoms; builder.beginUnit(); for (let j = 0, _j = OrderedSet.size(set); j < _j; j++) { @@ -56,11 +63,11 @@ function atomGroupsLinear(atomTest: Atom.Predicate): Query { builder.commitUnit(unitId); } - return Structure.create(units, builder.getSet()); + return Selection.Singletons(structure, builder.getSet()); }; } -function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomGroupsParams): Query { +function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomGroupsQueryParams): Query { return structure => { const { atoms, units } = structure; const unitIds = AtomSet.unitIds(atoms); @@ -71,7 +78,7 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A const unitId = unitIds[i]; const unit = units[unitId]; l.unit = unit; - const set = AtomSet.unitGetByIndex(atoms, i); + const set = AtomSet.unitGetByIndex(atoms, i).atoms; builder.beginUnit(); const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set); @@ -99,20 +106,20 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A builder.commitUnit(unitId); } - return Structure.create(units, builder.getSet()); + return Selection.Singletons(structure, builder.getSet()); }; } class LinearGroupingBuilder { private builders: AtomSet.Builder[] = []; - private builderMap: { [key: string]: AtomSet.Builder } = Object.create(null); + private builderMap = new Map<string, AtomSet.Builder>(); add(key: any, unit: number, atom: number) { - let b = this.builderMap[key]; + let b = this.builderMap.get(key); if (!b) { b = AtomSet.LinearBuilder(this.structure.atoms); this.builders[this.builders.length] = b; - this.builderMap[key] = b; + this.builderMap.set(key, b); } b.add(unit, atom); } @@ -124,34 +131,33 @@ class LinearGroupingBuilder { return true; } - private singletonStructure(): Structure { + private singletonSelection(): Selection { const atoms: Atom[] = Atom.createEmptyArray(this.builders.length); for (let i = 0, _i = this.builders.length; i < _i; i++) { atoms[i] = this.builders[i].singleton(); } - return Structure.create(this.structure.units, AtomSet.create(atoms)); + return Selection.Singletons(this.structure, AtomSet.ofAtoms(atoms, this.structure.atoms)); } private fullSelection() { - const ret: Structure[] = []; + const sets: AtomSet[] = new Array(this.builders.length); for (let i = 0, _i = this.builders.length; i < _i; i++) { - ret[i] = Structure.create(this.structure.units, this.builders[i].getSet()); + sets[i] = this.builders[i].getSet(); } - return ret; + return Selection.Sequence(this.structure, sets); } getSelection(): Selection { const len = this.builders.length; - if (len === 0) return Selection.Empty; - if (len === 1) return Structure.create(this.structure.units, this.builders[0].getSet()); - if (this.allSingletons()) return this.singletonStructure(); + if (len === 0) return Selection.Empty(this.structure); + if (this.allSingletons()) return this.singletonSelection(); return this.fullSelection(); } constructor(private structure: Structure) { } } -function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsParams): Query { +function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query { return structure => { const { atoms, units } = structure; const unitIds = AtomSet.unitIds(atoms); @@ -162,7 +168,7 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group const unitId = unitIds[i]; const unit = units[unitId]; l.unit = unit; - const set = AtomSet.unitGetByIndex(atoms, i); + const set = AtomSet.unitGetByIndex(atoms, i).atoms; const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set); const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set); diff --git a/src/mol-model/structure/query/predicates.ts b/src/mol-model/structure/query/predicates.ts index 772eb803022029425e2d6bd11792585601da278c..9af0bafbaa9b9bb9a56479492a487cafb0689296 100644 --- a/src/mol-model/structure/query/predicates.ts +++ b/src/mol-model/structure/query/predicates.ts @@ -64,7 +64,7 @@ namespace Predicates { export function or(...ps: Atom.Predicate[]): Atom.Predicate { switch (ps.length) { - case 0: return P.constant.true; + case 0: return P.constant.false; case 1: return ps[0]; case 2: { const a = ps[0], b = ps[1]; diff --git a/src/mol-model/structure/query/query.ts b/src/mol-model/structure/query/query.ts index 20c569b6d96b66351c2aca3fb2837f056bb47c49..001f1e0e79b3082011d3b60baecfa2a67e88befa 100644 --- a/src/mol-model/structure/query/query.ts +++ b/src/mol-model/structure/query/query.ts @@ -7,5 +7,7 @@ import { Structure } from '../structure' import Selection from './selection' +// TODO: Query { (s: Structure): Computation<Selection> } + interface Query { (s: Structure): Selection } export default Query \ No newline at end of file diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index 8504e425237b3761fdb7c3704aef40f3b8fd1432..2d8467e0764f13993077c5d6a49761ec66874acc 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -4,121 +4,111 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Iterator from 'mol-data/iterator' -import HashSet from 'mol-data/util/hash-set' -import { Structure, Atom, AtomSet } from '../structure' +import { HashSet } from 'mol-data/util' +import { Structure, AtomSet } from '../structure' -type Selection = - | Structure // each atom is interpreted as a singleton structure - | Structure[] +// A selection is a pair of a Structure and a sequence of unique AtomSets +type Selection = Selection.Singletons | Selection.Sequence namespace Selection { - export const Empty: Selection = []; + // If each element of the selection is a singleton, we can use a more efficient representation. + export interface Singletons { readonly kind: 'singletons', readonly structure: Structure, readonly set: AtomSet } + export interface Sequence { readonly kind: 'sequence', readonly structure: Structure, readonly sets: ReadonlyArray<AtomSet> } - function isStructure(x: Selection): x is Structure { return !!(x as Structure).units && !!(x as Structure).atoms; } + export function Singletons(structure: Structure, set: AtomSet): Singletons { return { kind: 'singletons', structure, set } } + export function Sequence(structure: Structure, sets: AtomSet[]): Sequence { return { kind: 'sequence', structure, sets } } + export function Empty(structure: Structure): Selection { return Sequence(structure, []); }; + + export function isSingleton(s: Selection): s is Singletons { return s.kind === 'singletons'; } + export function isEmpty(s: Selection) { return isSingleton(s) ? AtomSet.atomCount(s.set) === 0 : s.sets.length === 0; } export function structureCount(sel: Selection) { - if (isStructure(sel)) return AtomSet.atomCount(sel.atoms); - return sel.length; + if (isSingleton(sel)) return AtomSet.atomCount(sel.set); + return sel.sets.length; } - export function union(sel: Selection): Structure { - if (isStructure(sel)) return sel; - if (!sel.length) return Structure.Empty; - const sets = []; - for (let i = 0, _i = sel.length; i < _i; i++) sets[sets.length] = sel[i].atoms; - return Structure.create(unionUnits(sel), AtomSet.unionMany(sets)); + export function unionStructure(sel: Selection): Structure { + if (isEmpty(sel)) return Structure.Empty(sel.structure.units); + if (isSingleton(sel)) return Structure.create(sel.structure.units, sel.set); + return Structure.create(sel.structure.units, AtomSet.union(sel.sets, sel.structure.atoms)); } - export function structures(sel: Selection): Iterator<Structure> { - if (isStructure(sel)) { - const units = sel.units; - return Iterator.map<Atom, Structure>(AtomSet.atoms(sel.atoms), atoms => Structure.create(units, atoms)); + export function getAt(sel: Selection, i: number): Structure { + if (isSingleton(sel)) { + const atom = AtomSet.atomGetAt(sel.set, i); + return Structure.create(sel.structure.units, AtomSet.singleton(atom, sel.structure.atoms)); } - return Iterator.Array(sel); + return Structure.create(sel.structure.units, sel.sets[i]); } - export function getAt(sel: Selection, i: number): Structure { - if (isStructure(sel)) { - return Structure.create(sel.units, AtomSet.atomGetAt(sel.atoms, i)); + export function toStructures(sel: Selection): Structure[] { + const { units } = sel.structure; + if (isSingleton(sel)) { + const ret: Structure[] = new Array(AtomSet.atomCount(sel.set)); + const atoms = AtomSet.atoms(sel.set); + let offset = 0; + while (atoms.hasNext) { + const atom = atoms.move(); + ret[offset++] = Structure.create(units, AtomSet.singleton(atom, sel.structure.atoms)) + } + return ret; + } else { + const { sets } = sel; + const ret: Structure[] = new Array(sets.length); + for (let i = 0, _i = sets.length; i < _i; i++) ret[i] = Structure.create(units, sets[i]); + return ret; } - return sel[i]; } export interface Builder { - add(s: Structure): void, + add(set: AtomSet): void, getSelection(): Selection } + function getSelection(structure: Structure, sets: AtomSet[], allSingletons: boolean) { + const len = sets.length; + if (len === 0) return Empty(structure); + if (allSingletons) return Singletons(structure, AtomSet.union(sets, structure.atoms)); + return Sequence(structure, sets); + } + class LinearBuilderImpl implements Builder { - private structures: Structure[] = []; + private sets: AtomSet[] = []; private allSingletons = true; - add(s: Structure) { - const atomCount = AtomSet.atomCount(s.atoms); + add(atoms: AtomSet) { + const atomCount = AtomSet.atomCount(atoms); if (atomCount === 0) return; - this.structures[this.structures.length] = s; + this.sets[this.sets.length] = atoms; if (atomCount !== 1) this.allSingletons = false; } - getSelection() { - const len = this.structures.length; - if (len === 0) return Empty; - if (len === 1) return this.structures[0]; - if (this.allSingletons) return union(this.structures); - return this.structures; - } + getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); } - constructor() { } + constructor(private structure: Structure) { } } class HashBuilderImpl implements Builder { - private structures: Structure[] = []; + private sets: AtomSet[] = []; private allSingletons = true; - private sets = HashSet(AtomSet.hashCode, AtomSet.areEqual); + private uniqueSets = HashSet(AtomSet.hashCode, AtomSet.areEqual); - add(s: Structure) { - const atomCount = AtomSet.atomCount(s.atoms); - if (atomCount === 0 || !this.sets.add(s.atoms)) return; - this.structures[this.structures.length] = s; + add(atoms: AtomSet) { + const atomCount = AtomSet.atomCount(atoms); + if (atomCount === 0 || !this.uniqueSets.add(atoms)) return; + this.sets[this.sets.length] = atoms; if (atomCount !== 1) this.allSingletons = false; } - getSelection() { - const len = this.structures.length; - if (len === 0) return Empty; - if (len === 1) return this.structures[0]; - if (this.allSingletons) return union(this.structures); - return this.structures; - } + getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); } - constructor() { } + constructor(private structure: Structure) { } } - export function LinearBuilder(): Builder { return new LinearBuilderImpl(); } - export function UniqueBuilder(): Builder { return new HashBuilderImpl(); } + export function LinearBuilder(structure: Structure): Builder { return new LinearBuilderImpl(structure); } + export function UniqueBuilder(structure: Structure): Builder { return new HashBuilderImpl(structure); } // TODO: spatial lookup } -export default Selection - -function unionUnits(xs: Structure[]): Structure['units'] { - let prev = xs[0].units; - let sameModel = true; - for (let i = 1, _i = xs.length; i < _i; i++) { - if (xs[i].units !== prev) sameModel = false; - } - if (sameModel) return prev; - - let ret: any = { ...prev }; - for (let i = 1, _i = xs.length; i < _i; i++) { - const units = xs[i].units; - if (units !== prev) { - const keys = Object.keys(units); - for (let j = 0; j < keys.length; j++) ret[keys[j]] = (units as any)[keys[j]]; - } - prev = xs[i]; - } - return ret; -} +export default Selection \ No newline at end of file diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts index 4fe835cfaffe28eeb60b503fc14b6e713f2074b5..fbd00475512b275cd41c23c562f3ccf75adaa336 100644 --- a/src/mol-model/structure/structure.ts +++ b/src/mol-model/structure/structure.ts @@ -6,7 +6,9 @@ import Atom from './structure/atom' import AtomSet from './structure/atom/set' +import AtomGroup from './structure/atom/group' import Structure from './structure/structure' import Unit from './structure/unit' +import Symmetry from './structure/symmetry' -export { Atom, AtomSet, Structure, Unit } \ No newline at end of file +export { Atom, AtomSet, AtomGroup, Structure, Unit, Symmetry } \ No newline at end of file diff --git a/src/mol-model/structure/structure/atom/group.ts b/src/mol-model/structure/structure/atom/group.ts new file mode 100644 index 0000000000000000000000000000000000000000..63228158aa1e4c05481ba9ae2e1e8ae27e3ac7e3 --- /dev/null +++ b/src/mol-model/structure/structure/atom/group.ts @@ -0,0 +1,71 @@ +/** + * 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 Unit from '../unit' + +interface AtomGroup { + atoms: OrderedSet, + id: number +} + +namespace AtomGroup { + export const Empty = createNew(OrderedSet.Empty) + + export function singleton(idx: number) { + return createNew(OrderedSet.ofSingleton(idx)); + } + + export function createNew(atoms: OrderedSet): AtomGroup { + return { id: nextId(), atoms }; + } + + export function create(unit: Unit, atoms: OrderedSet): AtomGroup { + if (OrderedSet.areEqual(atoms, unit.fullGroup.atoms)) return unit.fullGroup; + return createNew(atoms); + } + + export function createChild(parent: AtomGroup, atoms: OrderedSet): AtomGroup { + if (OrderedSet.areEqual(atoms, parent.atoms)) return parent; + return createNew(atoms); + } + + export function size(group: AtomGroup) { return OrderedSet.size(group.atoms); } + export function has(group: AtomGroup, atom: number) { return OrderedSet.has(group.atoms, atom); } + export function getAt(group: AtomGroup, i: number) { return OrderedSet.getAt(group.atoms, i); } + export function indexOf(group: AtomGroup, atom: number) { return OrderedSet.indexOf(group.atoms, atom); } + export function hashCode(group: AtomGroup) { return OrderedSet.hashCode(group.atoms); } + export function areEqual(a: AtomGroup, b: AtomGroup) { return OrderedSet.areEqual(a.atoms, b.atoms); } + + export function intersect(a: AtomGroup, b: AtomGroup) { + const set = OrderedSet.intersect(a.atoms, b.atoms); + if (set === a.atoms) return a; + if (set === b.atoms) return b; + return createNew(set); + } + + export function union(a: AtomGroup, b: AtomGroup) { + const set = OrderedSet.union(a.atoms, b.atoms); + if (set === a.atoms) return a; + if (set === b.atoms) return b; + return createNew(set); + } + + export function subtract(a: AtomGroup, b: AtomGroup) { + const set = OrderedSet.subtract(a.atoms, b.atoms); + if (set === a.atoms) return a; + return createNew(set); + } + + let _id = 0; + function nextId() { + const ret = _id; + _id = (_id + 1) % 0x3fffffff; + return ret; + } +} + +export default AtomGroup \ No newline at end of file diff --git a/src/mol-model/structure/structure/atom/set/properties.ts b/src/mol-model/structure/structure/atom/impl/properties.ts similarity index 100% rename from src/mol-model/structure/structure/atom/set/properties.ts rename to src/mol-model/structure/structure/atom/impl/properties.ts diff --git a/src/mol-model/structure/structure/atom/set/builder.ts b/src/mol-model/structure/structure/atom/impl/set-builder.ts similarity index 55% rename from src/mol-model/structure/structure/atom/set/builder.ts rename to src/mol-model/structure/structure/atom/impl/set-builder.ts index fdda1a1c5c0d658c19f93d84953122abd276a08d..84c754775dbe0e14ef82ba78c1636b53d88051ce 100644 --- a/src/mol-model/structure/structure/atom/set/builder.ts +++ b/src/mol-model/structure/structure/atom/impl/set-builder.ts @@ -6,21 +6,21 @@ import AtomSet from '../set' import Atom from '../../atom' -import { OrderedSet } from 'mol-data/int' +import { OrderedSet, IntMap } from 'mol-data/int' import { sortArray } from 'mol-data/util/sort' export class Builder { private keys: number[] = []; - private units: number[][] = Object.create(null); + private units = IntMap.Mutable<number[]>(); private currentUnit: number[] = []; atomCount = 0; add(u: number, a: number) { - const unit = this.units[u]; + const unit = this.units.get(u); if (!!unit) { unit[unit.length] = a; } else { - this.units[u] = [a]; + this.units.set(u, [a]); this.keys[this.keys.length] = u; } this.atomCount++; @@ -31,40 +31,35 @@ export class Builder { commitUnit(u: number) { if (this.currentUnit.length === 0) return; this.keys[this.keys.length] = u; - this.units[u] = this.currentUnit; + this.units.set(u, this.currentUnit); } getSet(): AtomSet { - const sets: { [key: number]: OrderedSet } = Object.create(null); - - let allEqual = this.keys.length === AtomSet.unitCount(this.parent); + const generator = AtomSet.TemplateGenerator(this.parent); for (let i = 0, _i = this.keys.length; i < _i; i++) { const k = this.keys[i]; - const unit = this.units[k]; + const unit = this.units.get(k); const l = unit.length; if (!this.sorted && l > 1) sortArray(unit); - - const set = l === 1 ? OrderedSet.ofSingleton(unit[0]) : OrderedSet.ofSortedArray(unit); - const parentSet = AtomSet.unitGetById(this.parent, k); - if (OrderedSet.areEqual(set, parentSet)) { - sets[k] = parentSet; - } else { - sets[k] = set; - allEqual = false; - } + generator.add(k, OrderedSet.ofSortedArray(unit)); } - return allEqual ? this.parent : AtomSet.create(sets); + + return generator.getSet(); } singleton(): Atom { const u = this.keys[0]; - return Atom.create(u, this.units[u][0]); + return Atom.create(u, this.units.get(u)[0]); } constructor(private parent: AtomSet, private sorted: boolean) { } } -export default function createBuilder(parent: AtomSet, sorted: boolean) { - return new Builder(parent, sorted); +export function LinearBuilder(parent: AtomSet) { + return new Builder(parent, true); +} + +export function UnsortedBuilder(parent: AtomSet) { + return new Builder(parent, false); } \ No newline at end of file diff --git a/src/mol-model/structure/structure/atom/impl/set.ts b/src/mol-model/structure/structure/atom/impl/set.ts new file mode 100644 index 0000000000000000000000000000000000000000..403af68a9f695902a80e84bc45d8375543f29bd9 --- /dev/null +++ b/src/mol-model/structure/structure/atom/impl/set.ts @@ -0,0 +1,448 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { SortedArray, Interval, Iterator, OrderedSet as OS, IntMap } from 'mol-data/int' +import { sortArray } from 'mol-data/util/sort' +import { hash1 } from 'mol-data/util/hash-functions' +import Atom from '../../atom' +import AtomGroup from '../group' + +/** Long and painful implementation starts here */ + +export type AtomSetImpl = { groups: IntMap<AtomGroup>, offsets: Int32Array, hashCode: number, keys: SortedArray } + +export const Empty: AtomSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty }; + +export function ofAtoms(atoms: ArrayLike<Atom>, template: AtomSetImpl): AtomSetImpl { + return ofAtomsImpl(atoms, template); +} + +export function singleton(atom: Atom, template: AtomSetImpl) { + return singletonImpl(atom, template); +} + +export function getKeys(set: AtomSetImpl): SortedArray { + return set.keys; +} + +export function keyCount(set: AtomSetImpl): number { + return set.keys.length; +} + +export function hasKey(set: AtomSetImpl, key: number): boolean { + return set.groups.has(key); +} + +export function getKey(set: AtomSetImpl, index: number): number { + return set.keys[index]; +} + +export function hasAtom(set: AtomSetImpl, t: Atom): boolean { + const os = set.groups.get(Atom.unit(t)); + return !!os && AtomGroup.has(os, Atom.index(t)); +} + +export function getByKey(set: AtomSetImpl, key: number): AtomGroup { + return set.groups.get(key) || AtomGroup.Empty; +} + +export function getByIndex(set: AtomSetImpl, index: number): AtomGroup { + const key = set.keys[index]; + return set.groups.get(key) || AtomGroup.Empty; +} + +export function getAt(set: AtomSetImpl, i: number): Atom { + const { offsets, keys } = set; + const o = getOffsetIndex(offsets, i); + if (o >= offsets.length - 1) return Atom.Zero; + const k = keys[o]; + const e = AtomGroup.getAt(set.groups.get(k), i - offsets[o]); + return Atom.create(k, e); +} + +export function indexOf(set: AtomSetImpl, t: Atom) { + const { keys } = set; + const u = Atom.unit(t); + const setIdx = SortedArray.indexOf(keys, u); + if (setIdx < 0) return -1; + const o = AtomGroup.indexOf(set.groups.get(u), Atom.index(t)); + if (o < 0) return -1; + return set.offsets[setIdx] + o; +} + +/** Number elements in the "child" sets */ +export function size(set: AtomSetImpl) { + return set.offsets[set.offsets.length - 1]; +} + +export function hashCode(set: AtomSetImpl) { + if (set.hashCode !== -1) return set.hashCode; + return computeHash(set); +} + +export function areEqual(a: AtomSetImpl, b: AtomSetImpl) { + if (a === b) return true; + if (size(a) !== size(a)) return false; + + const keys = a.keys; + if (!SortedArray.areEqual(keys, b.keys)) return false; + const { groups: aG } = a; + const { groups: bG } = b; + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; + if (!AtomGroup.areEqual(aG.get(k), bG.get(k))) return false; + } + return true; +} + +export function areIntersecting(a: AtomSetImpl, b: AtomSetImpl) { + if (a === b) return true; + const keysA = a.keys, keysB = b.keys; + if (!SortedArray.areIntersecting(a.keys, b.keys)) return false; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); + const start = Interval.start(r), end = Interval.end(r); + const { groups: aG } = a; + const { groups: bG } = b; + for (let i = start; i < end; i++) { + const k = keysA[i]; + const ak = aG.get(k), bk = bG.get(k); + if (!!ak && !!bk && OS.areIntersecting(ak.atoms, bk.atoms)) return true; + } + return false; +} + +export function intersect(a: AtomSetImpl, b: AtomSetImpl) { + if (a === b) return a; + + const keysA = a.keys, keysB = b.keys; + if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); + const start = Interval.start(r), end = Interval.end(r); + + const { groups: aG } = a; + const { groups: bG } = b; + const generator = new ChildGenerator(a, b); + for (let i = start; i < end; i++) { + const k = keysA[i]; + const bk = bG.get(k); + if (!bk) continue; + const ak = aG.get(k); + generator.add(k, AtomGroup.intersect(aG.get(k), bk), ak, bk); + } + return generator.getSet(); +} + +export function subtract(a: AtomSetImpl, b: AtomSetImpl) { + if (a === b) return Empty; + + const keysA = a.keys, keysB = b.keys; + if (!SortedArray.areIntersecting(keysA, keysB)) return a; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); + const start = Interval.start(r), end = Interval.end(r); + + const generator = new ChildGenerator(a, b); + const { groups: aG } = a; + const { groups: bG } = b; + for (let i = 0; i < start; i++) { + const k = keysA[i]; + const ak = aG.get(k); + generator.addA(k, ak, ak); + } + for (let i = start; i < end; i++) { + const k = keysA[i]; + const ak = aG.get(k), bk = bG.get(k); + if (!!bk) { + const subtraction = AtomGroup.subtract(ak, bk); + generator.addA(k, subtraction, ak); + } else { + generator.addA(k, ak, ak); + } + } + for (let i = end, _i = keysA.length; i < _i; i++) { + const k = keysA[i]; + const ak = aG.get(k); + generator.addA(k, ak, ak); + } + return generator.getSet(); +} + +export function unionMany(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) { + return findUnion(sets, template); +} + +class ElementsIterator implements Iterator<Atom> { + private unit: number = 0; + private keyCount: number; + private setIndex = -1; + private currentIndex = 0; + private currentSize = 0; + private currentSet: OS = OS.Empty; + + hasNext: boolean = false; + + move() { + if (!this.hasNext) return Atom.Zero; + const ret = Atom.create(this.unit, OS.getAt(this.currentSet, this.currentIndex++)); + if (this.currentIndex >= this.currentSize) this.advance(); + return ret; + } + + private advance() { + if (++this.setIndex >= this.keyCount) { + this.hasNext = false; + return false; + } + this.unit = this.elements.keys[this.setIndex]; + this.currentSet = this.elements.groups.get(this.unit).atoms; + this.currentIndex = 0; + this.currentSize = OS.size(this.currentSet); + return true; + } + + constructor(private elements: AtomSetImpl) { + this.keyCount = elements.keys.length; + this.hasNext = this.keyCount > 0; + this.advance(); + } +} + +export function values(set: AtomSetImpl): Iterator<Atom> { + return new ElementsIterator(set); +} + +export class TemplateAtomSetGenerator { + private keys: number[] = []; + private groups = IntMap.Mutable<AtomGroup>(); + private templateGroups: IntMap<AtomGroup>; + private equalGroups = 0; + + add(unit: number, set: OS) { + if (OS.size(set) === 0) return; + this.keys[this.keys.length] = unit; + if (this.templateGroups.has(unit)) { + const t = this.templateGroups.get(unit); + if (OS.areEqual(t.atoms, set)) { + this.groups.set(unit, t); + this.equalGroups++; + } else { + this.groups.set(unit, AtomGroup.createNew(set)); + } + } else { + this.groups.set(unit, AtomGroup.createNew(set)); + } + } + + getSet(): AtomSetImpl { + if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) { + return this.template; + } + return create(this.keys, this.groups); + } + + constructor(private template: AtomSetImpl) { + this.templateGroups = template.groups; + } +} + +export function TemplateGenerator(template: AtomSetImpl) { + return new TemplateAtomSetGenerator(template); +} + +export class AtomSetGenerator { + private keys: number[] = []; + private groups = IntMap.Mutable<AtomGroup>(); + + add(unit: number, group: AtomGroup) { + if (AtomGroup.size(group) === 0) return; + this.keys[this.keys.length] = unit; + this.groups.set(unit, group); + } + + getSet(): AtomSetImpl { + return create(this.keys, this.groups); + } +} + +export function Generator() { + return new AtomSetGenerator(); +} + +/** When adding groups, compare them to existing ones. If they all match, return the whole original set. */ +class ChildGenerator { + private keys: number[] = []; + private groups = IntMap.Mutable<AtomGroup>(); + private aEqual = 0; + private bEqual = 0; + + add(unit: number, group: AtomGroup, a: AtomGroup, b: AtomGroup) { + if (AtomGroup.size(group) === 0) return; + if (a === group) this.aEqual++; + if (b === group) this.bEqual++; + this.keys[this.keys.length] = unit; + this.groups.set(unit, group); + } + + addA(unit: number, group: AtomGroup, a: AtomGroup) { + if (AtomGroup.size(group) === 0) return; + + if (a === group) this.aEqual++; + this.keys[this.keys.length] = unit; + this.groups.set(unit, group); + } + + constructor(private a: AtomSetImpl, private b: AtomSetImpl) { + } + + getSet(): AtomSetImpl { + if (this.aEqual === this.a.keys.length) return this.a; + if (this.bEqual === this.b.keys.length) return this.b; + return create(this.keys, this.groups); + } +} + +function create(keys: number[], groups: IntMap<AtomGroup>): AtomSetImpl { + sortArray(keys); + let runningSize = 0; + const offsets = new Int32Array(keys.length + 1); + for (let i = 0, _i = keys.length; i < _i; i++) { + runningSize += AtomGroup.size(groups.get(keys[i])); + offsets[i + 1] = runningSize; + } + return { keys: SortedArray.ofSortedArray(keys), groups: IntMap.asImmutable(groups), offsets, hashCode: -1 }; +} + +function getUniqueElements(xs: number[]): number[] { + let count = 1; + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] !== xs[i]) count++; + } + const ret = new (xs as any).constructor(count); + ret[0] = xs[0]; + let offset = 1; + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i]; + } + return ret; +} + +function normalizeArray(xs: number[]): number[] { + sortArray(xs); + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] === xs[i]) return getUniqueElements(xs); + } + return xs; +} + +function ofAtomsImpl(xs: ArrayLike<Atom>, template: AtomSetImpl) { + if (xs.length === 0) return Empty; + + const elements = IntMap.Mutable<number[]>(); + const keys: number[] = []; + for (let i = 0, _i = xs.length; i < _i; i++) { + const x = xs[i]; + const u = Atom.unit(x), v = Atom.index(x); + if (elements.has(u)) { + const set = elements.get(u); + set[set.length] = v; + } else { + keys[keys.length] = u; + elements.set(u, [v]); + } + } + + const generator = TemplateGenerator(template); + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; + generator.add(k, OS.ofSortedArray(normalizeArray(elements.get(k)))); + } + + return generator.getSet(); +} + +function singletonImpl(atom: Atom, template: AtomSetImpl) { + const k = Atom.unit(atom), i = Atom.index(atom); + const { groups } = template; + const gs = IntMap.Mutable<AtomGroup>(); + if (groups.has(k)) { + const g = groups.get(k); + if (AtomGroup.size(g) === 1 && AtomGroup.getAt(g, 0) === i) { + gs.set(k, g); + return create([k], gs); + } + } + gs.set(k, AtomGroup.createNew(OS.ofSingleton(i))); + return create([k], gs); +} + +function getOffsetIndex(xs: ArrayLike<number>, value: number) { + let min = 0, max = xs.length - 1; + while (min < max) { + const mid = (min + max) >> 1; + const v = xs[mid]; + if (value < v) max = mid - 1; + else if (value > v) min = mid + 1; + else return mid; + } + if (min > max) { + return max; + } + return value < xs[min] ? min - 1 : min; +} + +function computeHash(set: AtomSetImpl) { + const { keys, groups } = set; + let hash = 23; + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; + hash = (31 * hash + k) | 0; + hash = (31 * hash + AtomGroup.hashCode(groups.get(k))) | 0; + } + hash = (31 * hash + size(set)) | 0; + hash = hash1(hash); + set.hashCode = hash; + return hash; +} + +function findUnion(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) { + if (!sets.length) return Empty; + if (sets.length === 1) return sets[0]; + if (sets.length === 2 && sets[0] === sets[1]) return sets[0]; + + const keys: number[] = []; + const groups = IntMap.Mutable<AtomGroup>(); + for (let i = 0, _i = sets.length; i < _i; i++) { + unionInto(keys, groups, sets[i]); + } + + return normalizeUnion(keys, groups, template); +} + +function normalizeUnion(keys: number[], groups: IntMap.Mutable<AtomGroup>, template: AtomSetImpl) { + let equalCount = 0; + let tg = template.groups, a: AtomGroup, t: AtomGroup; + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; + if (tg.has(k) && AtomGroup.areEqual(a = groups.get(k), t = tg.get(k))) { + groups.set(k, t); + equalCount++; + } + } + return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups); +} + +function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSetImpl) { + const setKeys = a.keys; + const { groups: aG } = a; + for (let i = 0, _i = setKeys.length; i < _i; i++) { + const k = setKeys[i]; + 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)); + } + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/atom/set.ts b/src/mol-model/structure/structure/atom/set.ts index 3ce250e2477c195d6fa10ed29b12e7dd21c06371..318aa8b8583285b9d152d92f43e7468d0a68c725 100644 --- a/src/mol-model/structure/structure/atom/set.ts +++ b/src/mol-model/structure/structure/atom/set.ts @@ -4,24 +4,26 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { OrderedSet, SortedArray, Iterator } from 'mol-data/int' +import { SortedArray, Iterator, OrderedSet } from 'mol-data/int' import Atom from '../atom' -import * as Impl from './set/impl' -import createBuilder, { Builder as AtomSetBuilder } from './set/builder' +import AtomGroup from './group' +import * as Impl from './impl/set' +import * as Builders from './impl/set-builder' /** A map-like representation of grouped atom set */ namespace AtomSet { export const Empty: AtomSet = Impl.Empty as any; - export const create: (data: Atom | ArrayLike<Atom> | { [unitId: number]: OrderedSet }) => AtomSet = Impl.create as any; + export const ofAtoms: (atoms: ArrayLike<Atom>, template: AtomSet) => AtomSet = Impl.ofAtoms as any; + export const singleton: (atom: Atom, template: AtomSet) => AtomSet = Impl.singleton 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) => OrderedSet = Impl.getByKey as any; - export const unitGetByIndex: (set: AtomSet, i: number) => OrderedSet = Impl.getByIndex 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; @@ -33,14 +35,19 @@ namespace AtomSet { 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: (a: AtomSet, b: AtomSet) => AtomSet = Impl.union as any; - export const unionMany: (sets: AtomSet[]) => AtomSet = Impl.unionMany as any; + export const union: (sets: ArrayLike<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 = AtomSetBuilder - export function LinearBuilder(parent: AtomSet): Builder { return createBuilder(parent, true); } - export function UnsortedBuilder(parent: AtomSet): Builder { return createBuilder(parent, false); } + 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 interface TemplateGenerator { add(unit: number, set: OrderedSet): void, getSet(): AtomSet } + export const TemplateGenerator: (template: AtomSet) => TemplateGenerator = Impl.TemplateGenerator as any // TODO: bounding sphere // TODO: distance, areWithIn? diff --git a/src/mol-model/structure/structure/atom/set/impl.ts b/src/mol-model/structure/structure/atom/set/impl.ts deleted file mode 100644 index f60658f1f7ee283061f8cdd662811d714efdc804..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/atom/set/impl.ts +++ /dev/null @@ -1,497 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { SortedArray, Interval, Iterator, OrderedSet } from 'mol-data/int' -import { sortArray } from 'mol-data/util/sort' -import { hash1 } from 'mol-data/util/hash-functions' -import Atom from '../../atom' - -/** Long and painful implementation starts here */ - -export interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: SortedArray } -export type AtomSetImpl = Atom | AtomSetElements - -export const Empty: AtomSetImpl = { offsets: [0], hashCode: 0, keys: SortedArray.Empty }; - -export function create(data: Atom | ArrayLike<Atom> | { [id: number]: OrderedSet }): AtomSetImpl { - if (typeof data === 'number' || Atom.is(data)) return data; - if (isArrayLike(data)) return ofAtoms(data); - return ofObject(data as { [id: number]: OrderedSet }); -} - -export function isSingleton(set: AtomSetImpl) { - return typeof set === 'number'; -} - -export function getKeys(set: AtomSetImpl): SortedArray { - if (typeof set === 'number') return SortedArray.ofSingleton(set); - return (set as AtomSetElements).keys; -} - -export function keyCount(set: AtomSetImpl): number { - if (typeof set === 'number') return 1; - return (set as AtomSetElements).keys.length; -} - -export function hasKey(set: AtomSetImpl, key: number): boolean { - if (typeof set === 'number') return Atom.unit(set) === key; - return !!(set as AtomSetElements)[key] -} - -export function getKey(set: AtomSetImpl, index: number): number { - if (typeof set === 'number') return Atom.unit(set); - return (set as AtomSetElements).keys[index]; -} - -export function hasAtom(set: AtomSetImpl, t: Atom): boolean { - if (typeof set === 'number') return Atom.areEqual(t, set); - const os = (set as AtomSetElements)[Atom.unit(t)]; - return !!os && OrderedSet.has(os, Atom.index(t)); -} - -export function getByKey(set: AtomSetImpl, key: number): OrderedSet { - if (typeof set === 'number') { - return Atom.unit(set) === key ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty; - } - return (set as AtomSetElements)[key] || OrderedSet.Empty; -} - -export function getByIndex(set: AtomSetImpl, index: number): OrderedSet { - if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty; - const key = (set as AtomSetElements).keys[index]; - return (set as AtomSetElements)[key] || OrderedSet.Empty; -} - -export function getAt(set: AtomSetImpl, i: number): Atom { - if (typeof set === 'number') return set; - return getAtE(set as AtomSetElements, i); -} - -export function indexOf(set: AtomSetImpl, t: Atom) { - if (typeof set === 'number') return Atom.areEqual(set, t) ? 0 : -1; - return indexOfE(set as AtomSetElements, t); -} - -/** Number elements in the "child" sets */ -export function size(set: AtomSetImpl) { - if (typeof set === 'number') return 1; - return (set as AtomSetElements).offsets[(set as AtomSetElements).offsets.length - 1]; -} - -export function hashCode(set: AtomSetImpl) { - if (typeof set === 'number') return Atom.hashCode(set); - if ((set as AtomSetElements).hashCode !== -1) return (set as AtomSetElements).hashCode; - return computeHash((set as AtomSetElements)); -} - -export function areEqual(a: AtomSetImpl, b: AtomSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return Atom.areEqual(a, b); - return false; - } - if (typeof b === 'number') return false; - return areEqualEE(a as AtomSetElements, b as AtomSetElements); -} - -export function areIntersecting(a: AtomSetImpl, b: AtomSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return Atom.areEqual(a, b); - return areIntersectingNE(a, b as AtomSetElements); - } - if (typeof b === 'number') return areIntersectingNE(b, a as AtomSetElements); - return areIntersectingEE(a as AtomSetElements, b as AtomSetElements); -} - -export function intersect(a: AtomSetImpl, b: AtomSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return Atom.areEqual(a, b) ? a : Empty; - return intersectNE(a, b as AtomSetElements); - } - if (typeof b === 'number') return intersectNE(b, a as AtomSetElements); - return intersectEE(a as AtomSetElements, b as AtomSetElements); -} - -export function subtract(a: AtomSetImpl, b: AtomSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return Atom.areEqual(a, b) ? Empty : a; - return subtractNE(a, b as AtomSetElements); - } - if (typeof b === 'number') return subtractEN(a as AtomSetElements, b); - return subtractEE(a as AtomSetElements, b as AtomSetElements); -} - -export function union(a: AtomSetImpl, b: AtomSetImpl) { - return findUnion([a, b]); -} - -export function unionMany(sets: ArrayLike<AtomSetImpl>) { - return findUnion(sets); -} - -class ElementsIterator implements Iterator<Atom> { - private unit: number = 0; - private keyCount: number; - private setIndex = -1; - private currentIndex = 0; - private currentSize = 0; - private currentSet: OrderedSet = OrderedSet.Empty; - - hasNext: boolean = false; - - move() { - if (!this.hasNext) return Atom.Zero; - const ret = Atom.create(this.unit, OrderedSet.getAt(this.currentSet, this.currentIndex++)); - if (this.currentIndex >= this.currentSize) this.advance(); - return ret; - } - - private advance() { - if (++this.setIndex >= this.keyCount) { - this.hasNext = false; - return false; - } - this.unit = this.elements.keys[this.setIndex]; - this.currentSet = this.elements[this.unit]; - this.currentIndex = 0; - this.currentSize = OrderedSet.size(this.currentSet); - return true; - } - - constructor(private elements: AtomSetElements) { - this.keyCount = elements.keys.length; - this.hasNext = this.keyCount > 0; - this.advance(); - } -} - -export function values(set: AtomSetImpl): Iterator<Atom> { - if (typeof set === 'number') return Iterator.Value(set as Atom); - return new ElementsIterator(set as AtomSetElements); -} - -function isArrayLike(x: any): x is ArrayLike<Atom> { - return x && (typeof x.length === 'number' && (Array.isArray(x) || !!x.buffer)); -} - -function ofObject(data: { [id: number]: OrderedSet }) { - const keys = []; - - const _keys = Object.keys(data); - for (let i = 0, _i = _keys.length; i < _i; i++) { - const k = +_keys[i]; - if (OrderedSet.size(data[k]) > 0) keys[keys.length] = k; - } - if (!keys.length) return Empty; - if (keys.length === 1) { - const set = data[keys[0]]; - if (OrderedSet.size(set) === 1) return Atom.create(keys[0], OrderedSet.getAt(set, 0)); - } - return ofObject1(keys, data); -} - -function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { - if (keys.length === 1) { - const k = keys[0]; - const set = data[k]; - if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0)); - } - sortArray(keys); - return _createObjectOrdered(SortedArray.ofSortedArray(keys), data); -} - -function ofObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) { - if (keys.length === 1) { - const k = keys[0]; - const set = data[k]; - if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0)); - } - return _createObjectOrdered(keys, data); -} - -function _createObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) { - const ret: AtomSetElements = Object.create(null); - ret.keys = keys; - const offsets = [0]; - let runningSize = 0; - for (let i = 0, _i = keys.length; i < _i; i++) { - const k = keys[i]; - const set = data[k]; - ret[k] = set; - runningSize += OrderedSet.size(set); - offsets[offsets.length] = runningSize; - } - ret.offsets = offsets; - ret.hashCode = -1; - return ret; -} - -function getUniqueElements(xs: number[]) { - let count = 1; - for (let i = 1, _i = xs.length; i < _i; i++) { - if (xs[i - 1] !== xs[i]) count++; - } - const ret = new (xs as any).constructor(count); - ret[0] = xs[0]; - let offset = 1; - for (let i = 1, _i = xs.length; i < _i; i++) { - if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i]; - } - return ret; -} - -function normalizeArray(xs: number[]) { - sortArray(xs); - for (let i = 1, _i = xs.length; i < _i; i++) { - if (xs[i - 1] === xs[i]) return getUniqueElements(xs); - } - return xs; -} - -function ofAtoms(xs: ArrayLike<Atom>) { - if (xs.length === 0) return Empty; - const sets: { [key: number]: number[] } = Object.create(null); - for (let i = 0, _i = xs.length; i < _i; i++) { - const x = xs[i]; - const u = Atom.unit(x), v = Atom.index(x); - const set = sets[u]; - if (set) set[set.length] = v; - else sets[u] = [v]; - } - const ret: { [key: number]: OrderedSet } = Object.create(null); - const keys = []; - const _keys = Object.keys(sets); - for (let i = 0, _i = _keys.length; i < _i; i++) { - const k = +_keys[i]; - keys[keys.length] = k; - ret[k] = OrderedSet.ofSortedArray(normalizeArray(sets[k])); - } - return ofObject1(keys, ret); -} - -function getOffsetIndex(xs: ArrayLike<number>, value: number) { - let min = 0, max = xs.length - 1; - while (min < max) { - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - if (min > max) { - return max; - } - return value < xs[min] ? min - 1 : min; -} - -function getAtE(set: AtomSetElements, i: number): Atom { - const { offsets, keys } = set; - const o = getOffsetIndex(offsets, i); - if (o >= offsets.length - 1) return 0 as any; - const k = keys[o]; - const e = OrderedSet.getAt(set[k], i - offsets[o]); - return Atom.create(k, e); -} - -function indexOfE(set: AtomSetElements, t: Atom) { - const { keys } = set; - const u = Atom.unit(t); - const setIdx = SortedArray.indexOf(keys, u); - if (setIdx < 0) return -1; - const o = OrderedSet.indexOf(set[u], Atom.index(t)); - if (o < 0) return -1; - return set.offsets[setIdx] + o; -} - -function computeHash(set: AtomSetElements) { - const { keys } = set; - let hash = 23; - for (let i = 0, _i = keys.length; i < _i; i++) { - const k = keys[i]; - hash = (31 * hash + k) | 0; - hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0; - } - hash = (31 * hash + size(set)) | 0; - hash = hash1(hash); - set.hashCode = hash; - return hash; -} - -function areEqualEE(a: AtomSetElements, b: AtomSetElements) { - if (a === b) return true; - if (size(a) !== size(a)) return false; - - const keys = a.keys; - if (!SortedArray.areEqual(keys, b.keys)) return false; - for (let i = 0, _i = keys.length; i < _i; i++) { - const k = keys[i]; - if (!OrderedSet.areEqual(a[k], b[k])) return false; - } - return true; -} - -function areIntersectingNE(a: Atom, b: AtomSetElements) { - const u = Atom.unit(a); - return SortedArray.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a)); -} - -function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) { - if (a === b) return true; - const keysA = a.keys, keysB = b.keys; - if (!SortedArray.areIntersecting(a.keys, b.keys)) return false; - const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); - const start = Interval.start(r), end = Interval.end(r); - for (let i = start; i < end; i++) { - const k = keysA[i]; - const ak = a[k], bk = b[k]; - if (!!ak && !!bk && OrderedSet.areIntersecting(ak, bk)) return true; - } - return false; -} - -function intersectNE(a: Atom, b: AtomSetElements) { - const u = Atom.unit(a); - return !!b[u] && OrderedSet.has(b[u], Atom.index(a)) ? a : Empty; -} - -function intersectEE(a: AtomSetElements, b: AtomSetElements) { - if (a === b) return a; - - const keysA = a.keys, keysB = b.keys; - if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty; - const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); - const start = Interval.start(r), end = Interval.end(r); - - const keys = [], ret = Object.create(null); - for (let i = start; i < end; i++) { - const k = keysA[i]; - const bk = b[k]; - if (!bk) continue; - const intersection = OrderedSet.intersect(a[k], b[k]); - if (OrderedSet.size(intersection) > 0) { - keys[keys.length] = k; - ret[k] = intersection; - } - } - return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret); -} - -function subtractNE(a: Atom, b: AtomSetElements) { - return hasAtom(b, a) ? Empty : a; -} - -function subtractEN(a: AtomSetElements, b: Atom): AtomSetImpl { - if (!hasAtom(a, b)) return a; - - const u = Atom.unit(b), v = Atom.index(b); - const set = a[u]; - if (OrderedSet.size(set) === 1) { - // remove the entire unit. - return ofObjectOrdered(SortedArray.subtract(a.keys, SortedArray.ofSingleton(u)), a); - } else { - const ret: { [key: number]: OrderedSet } = Object.create(null); - for (let i = 0, _i = a.keys.length; i < _i; i++) { - const k = a.keys[i]; - if (k === u) { - ret[k] = OrderedSet.subtract(set, OrderedSet.ofSingleton(v)); - } else ret[k] = a[k]; - } - return ofObjectOrdered(a.keys, ret); - } -} - -function subtractEE(a: AtomSetElements, b: AtomSetElements) { - if (a === b) return Empty; - - const keysA = a.keys, keysB = b.keys; - if (!SortedArray.areIntersecting(keysA, keysB)) return a; - const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); - const start = Interval.start(r), end = Interval.end(r); - - const keys = [], ret = Object.create(null); - for (let i = 0; i < start; i++) { - const k = keysA[i]; - keys[keys.length] = k; - ret[k] = a[k]; - } - for (let i = start; i < end; i++) { - const k = keysA[i]; - const ak = a[k], bk = b[k]; - if (!!bk) { - const subtraction = OrderedSet.subtract(ak, bk); - if (OrderedSet.size(subtraction) > 0) { - keys[keys.length] = k; - ret[k] = subtraction; - } - } else { - keys[keys.length] = k; - ret[k] = a[k]; - } - } - for (let i = end, _i = keysA.length; i < _i; i++) { - const k = keysA[i]; - keys[keys.length] = k; - ret[k] = a[k]; - } - return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret); -} - -function findUnion(sets: ArrayLike<AtomSetImpl>) { - if (!sets.length) return Empty; - if (sets.length === 1) return sets[0]; - if (sets.length === 2 && areEqual(sets[0], sets[1])) return sets[0]; - - const eCount = { count: 0 }; - const ns = unionN(sets, eCount); - if (!eCount.count) return ns; - const ret = Object.create(null); - for (let i = 0, _i = sets.length; i < _i; i++) { - const s = sets[i]; - if (typeof s !== 'number') unionInto(ret, s as AtomSetElements); - } - if (size(ns as AtomSetImpl) > 0) { - if (typeof ns === 'number') unionIntoN(ret, ns as any); - else unionInto(ret, ns as AtomSetElements); - } - return ofObject(ret); -} - -function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) { - let countN = 0, countE = 0; - for (let i = 0, _i = sets.length; i < _i; i++) { - if (typeof sets[i] === 'number') countN++; - else countE++; - } - eCount.count = countE; - if (!countN) return Empty; - if (countN === sets.length) return ofAtoms(sets as ArrayLike<Atom>); - const packed = Atom.createEmptyArray(countN); - let offset = 0; - for (let i = 0, _i = sets.length; i < _i; i++) { - const s = sets[i]; - if (typeof s === 'number') packed[offset++] = s; - } - return ofAtoms(packed as any); -} - -function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) { - const keys = a.keys; - for (let i = 0, _i = keys.length; i < _i; i++) { - const k = keys[i]; - const set = data[k]; - if (set) data[k] = OrderedSet.union(set, a[k]); - else data[k] = a[k]; - } -} - -function unionIntoN(data: { [key: number]: OrderedSet }, a: Atom) { - const u = Atom.unit(a); - const set = data[u]; - if (set) { - data[u] = OrderedSet.union(set, OrderedSet.ofSingleton(Atom.index(a))); - } else { - data[u] = OrderedSet.ofSingleton(Atom.index(a)); - } -} \ 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 0d262041252f50b998b7465ce7625921028560c0..6cac5a7b8c4e05f9fdfc96abb12e902f68ae7bda 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -5,25 +5,24 @@ */ import { OrderedSet, Iterator } from 'mol-data/int' -import UniqueArray from 'mol-data/util/unique-array' +import { UniqueArray } from 'mol-data/util' import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { Model, Format } from '../model' import Unit from './unit' import AtomSet from './atom/set' +import AtomGroup from './atom/group' import Atom from './atom' - -interface Structure extends Readonly<{ - units: { readonly [id: number]: Unit }, - atoms: AtomSet -}> { } +// A structure is a pair of "units" and an atom set. +// Each unit contains the data and transformation of its corresponding atoms. +interface Structure { + readonly units: ReadonlyArray<Unit>, + readonly atoms: AtomSet +} namespace Structure { - export const Empty = { units: {}, atoms: AtomSet.Empty }; - - export function create(units: Structure['units'], atoms: AtomSet): Structure { - return { units, atoms }; - } + export function create(units: ReadonlyArray<Unit>, atoms: AtomSet): Structure { return { units, atoms }; } + export function Empty(units: ReadonlyArray<Unit>): Structure { return create(units, AtomSet.Empty); }; export function ofData(format: Format) { const models = Model.create(format); @@ -35,29 +34,32 @@ namespace Structure { const builder = Builder(); for (let c = 0; c < chains.count; c++) { - const unit = Unit.create(model, SymmetryOperator.Default); - builder.addUnit(unit); - builder.addAtoms(unit.id, OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1])); + const group = AtomGroup.createNew(OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1])); + const unit = Unit.create(model, SymmetryOperator.Default, group); + builder.add(unit, unit.fullGroup); } return builder.getStructure(); } export interface Builder { + add(unit: Unit, atoms: AtomGroup): void, addUnit(unit: Unit): void, - addAtoms(unitId: number, atoms: OrderedSet): void, + setAtoms(unitId: number, atoms: AtomGroup): void, getStructure(): Structure, readonly atomCount: number } class BuilderImpl implements Builder { - private units = Object.create(null); - private atoms = Object.create(null); + private _unitId = 0; + private units: Unit[] = []; + private atoms = AtomSet.Generator(); atomCount = 0; - addUnit(unit: Unit) { this.units[unit.id] = unit; } - addAtoms(unitId: number, atoms: OrderedSet) { this.atoms[unitId] = atoms; this.atomCount += OrderedSet.size(atoms); } - getStructure(): Structure { return this.atomCount > 0 ? Structure.create(this.units, AtomSet.create(this.atoms)) : Empty; } + add(unit: Unit, atoms: AtomGroup) { const id = this.addUnit(unit); this.setAtoms(id, atoms); } + addUnit(unit: Unit) { const id = this._unitId++; this.units[id] = unit; return id; } + setAtoms(unitId: number, atoms: AtomGroup) { this.atoms.add(unitId, atoms); this.atomCount += AtomGroup.size(atoms); } + getStructure(): Structure { return this.atomCount > 0 ? Structure.create(this.units, this.atoms.getSet()) : Empty(this.units); } } export function Builder(): Builder { return new BuilderImpl(); } @@ -70,9 +72,11 @@ namespace Structure { } export function getModels(s: Structure) { + const { units, atoms } = s; const arr = UniqueArray.create<Model['id'], Model>(); - for (const k of Object.keys(s.units)) { - const u = s.units[+k]; + const ids = AtomSet.unitIds(atoms); + for (let i = 0; i < ids.length; i++) { + const u = units[ids[i]]; UniqueArray.add(arr, u.model.id, u.model); } return arr.array; diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..d23962d722583bceb842bb2efa111ff7ea268926 --- /dev/null +++ b/src/mol-model/structure/structure/symmetry.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Structure from './structure' +import AtomSet from './atom/set' +import Unit from './unit' +import { Selection } from '../query' +import { ModelSymmetry } from '../model' + +namespace Symmetry { + export const buildAssembly = buildAssemblyImpl; +} + +export default Symmetry; + +function buildAssemblyImpl(structure: Structure, name: string) { + const models = Structure.getModels(structure); + if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.'); + + const assembly = ModelSymmetry.findAssembly(models[0], name); + if (!assembly) throw new Error(`Assembly '${name}' is not defined.`); + + const assembler = Structure.Builder(); + + for (const g of assembly.operatorGroups) { + const selection = g.selector(structure); + if (Selection.structureCount(selection) === 0) continue; + const { units, atoms } = Selection.unionStructure(selection); + + const unitIds = AtomSet.unitIds(atoms); + + for (const oper of g.operators) { + for (let uI = 0, _uI = unitIds.length; uI < _uI; uI++) { + const unit = units[unitIds[uI]]; + assembler.add(Unit.withOperator(unit, oper), AtomSet.unitGetByIndex(atoms, uI)); + } + } + } + + return assembler.getStructure(); +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index be83164a38f62a4d8dc99d4c190aa27d04c58343..2ec27b6ca0c2115af9388ee701ecaa1b6649f3cc 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -5,18 +5,26 @@ */ import SymmetryOperator from 'mol-math/geometry/symmetry-operator' +import AtomGroup from './atom/group' import { Model } from '../model' +// A bulding block of a structure that corresponds +// to a "natural group of atoms" (most often a "chain") +// together with a tranformation (rotation and translation) +// that is dynamically applied to the underlying atom set. +// +// An atom set can be referenced by multiple diffrent units which +// makes construction of assemblies and spacegroups very efficient. interface Unit extends SymmetryOperator.ArrayMapping { - // Structure-level unique identifier of the unit. - readonly id: number, - // Provides access to the underlying data. readonly model: Model, - // Determines the operation applied to this unit. - // The transform and and inverse are baked into the "getPosition" function - readonly operator: SymmetryOperator, + // The "full" atom group corresponding to this unit. + // Every selection is a subset of this atoms group. + // Things like inter-unit bonds or spatial lookups + // can be be implemented efficiently as "views" of the + // full group. + readonly fullGroup: AtomGroup, // Reference some commonly accessed things for faster access. readonly residueIndex: ArrayLike<number>, @@ -28,14 +36,14 @@ interface Unit extends SymmetryOperator.ArrayMapping { } namespace Unit { - export function create(model: Model, operator: SymmetryOperator): Unit { + export function create(model: Model, operator: SymmetryOperator, fullGroup: AtomGroup): Unit { const h = model.hierarchy; const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.conformation); return { - id: nextUnitId(), model, operator, + fullGroup, residueIndex: h.residueSegments.segmentMap, chainIndex: h.chainSegments.segmentMap, hierarchy: model.hierarchy, @@ -45,13 +53,10 @@ namespace Unit { x, y, z }; } -} -export default Unit; + export function withOperator(unit: Unit, operator: SymmetryOperator) { + return create(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup); + } +} -let _id = 0; -function nextUnitId() { - const ret = _id; - _id = (_id + 1) % 0x3fffffff; - return ret; -} \ No newline at end of file +export default Unit; \ No newline at end of file diff --git a/src/mol-ql/TODO b/src/mol-ql/TODO new file mode 100644 index 0000000000000000000000000000000000000000..e2a008501f360e80518129bb74699afacf9f8a3d --- /dev/null +++ b/src/mol-ql/TODO @@ -0,0 +1,2 @@ +- Have the ability to define schemas for output. + Define properties for each pattern/whatever that could be exported as CIF/CSV/JSON. \ No newline at end of file diff --git a/src/mol-util/computation.ts b/src/mol-util/computation.ts index 6bafb7a99b7159cc6fa0edc65554b86ab32e8458..3d956de70054e16b858a96c080393ac582517ab7 100644 --- a/src/mol-util/computation.ts +++ b/src/mol-util/computation.ts @@ -124,8 +124,6 @@ class ObservableContext implements Computation.Context { private observers: Computation.ProgressObserver[] | undefined = void 0; private progress: Computation.Progress = { message: 'Working...', current: 0, max: 0, elapsedMs: 0, isIndeterminate: true, requestAbort: void 0 }; - lastDelta = 0; - private checkAborted() { if (this.abortRequested) throw Computation.Aborted; } @@ -186,7 +184,6 @@ class ObservableContext implements Computation.Context { } } - this.lastDelta = time - this.lastUpdated; this.lastUpdated = time; return Scheduler.immediatePromise(); @@ -223,11 +220,13 @@ class ChunkerImpl implements Computation.Chunker { private processedSinceUpdate = 0; private updater: Computation.Context['update']; - private computeChunkSize() { - const lastDelta = (this.context as ObservableContext).lastDelta || 0; - if (!lastDelta) return this.nextChunkSize; + private computeChunkSize(delta: number) { + if (!delta) { + this.processedSinceUpdate = 0; + return this.nextChunkSize; + } const rate = (this.context as ObservableContext).updateRate || DefaulUpdateRateMs; - const ret = Math.round(this.processedSinceUpdate * rate / lastDelta + 1); + const ret = Math.round(this.processedSinceUpdate * rate / delta + 1); this.processedSinceUpdate = 0; return ret; } @@ -245,18 +244,34 @@ class ChunkerImpl implements Computation.Chunker { async process(nextChunk: (size: number) => number, update: (updater: Computation.Context['update']) => Promise<void> | void, nextChunkSize?: number) { if (typeof nextChunkSize !== 'undefined') this.setNextChunkSize(nextChunkSize); + this.processedSinceUpdate = 0; + // track time for the actual computation and exclude the "update time" + let chunkStart = Computation.now(); let lastChunkSize: number; + let chunkCount = 0; + let totalSize = 0; + let updateCount = 0; while ((lastChunkSize = nextChunk(this.getNextChunkSize())) > 0) { + chunkCount++; this.processedSinceUpdate += lastChunkSize; + totalSize += lastChunkSize; if (this.context.requiresUpdate) { + let time = Computation.now(); await update(this.updater); - this.nextChunkSize = this.computeChunkSize(); + this.nextChunkSize = updateCount > 0 + ? Math.round((totalSize + this.computeChunkSize(time - chunkStart)) / (chunkCount + 1)) + : this.computeChunkSize(time - chunkStart) + updateCount++; + chunkStart = Computation.now(); } } if (this.context.requiresUpdate) { + let time = Computation.now(); await update(this.updater); - this.nextChunkSize = this.computeChunkSize(); + this.nextChunkSize = updateCount > 0 + ? Math.round((totalSize + this.computeChunkSize(time - chunkStart)) / (chunkCount + 1)) + : this.computeChunkSize(time - chunkStart) } } diff --git a/src/mol-util/scheduler.ts b/src/mol-util/scheduler.ts index 85ad75eff32fcfe5e464aa876a76e449849f40af..e118ec97cbf221e6b21d7126687f39bcc78753e7 100644 --- a/src/mol-util/scheduler.ts +++ b/src/mol-util/scheduler.ts @@ -89,7 +89,7 @@ function createImmediateActions() { function canUsePostMessage() { // The test against `importScripts` prevents this implementation from being installed inside a web worker, // where `global.postMessage` means something completely different and can't be used for this purpose. - const global = window as any; + const global = typeof window !== 'undefined' ? window as any : void 0; if (global && global.postMessage && !global.importScripts) { let postMessageIsAsynchronous = true; const oldOnMessage = global.onmessage; @@ -108,6 +108,7 @@ function createImmediateActions() { // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages const messagePrefix = 'setImmediate$' + Math.random() + '$'; + const global = typeof window !== 'undefined' ? window as any : void 0; const onGlobalMessage = function(event: any) { if (event.source === global && typeof event.data === 'string' && @@ -187,7 +188,9 @@ function createImmediateActions() { const immediateActions = (function () { if (typeof setImmediate !== 'undefined') { - return { setImmediate, clearImmediate }; + if (typeof window !== 'undefined') { + return { setImmediate: (handler: any, ...args: any[]) => window.setImmediate(handler, ...args as any), clearImmediate: (handle: any) => window.clearImmediate(handle) }; + } else return { setImmediate, clearImmediate } } return createImmediateActions(); }()); diff --git a/src/perf-tests/chunked-array-vs-native.ts b/src/perf-tests/chunked-array-vs-native.ts index 1b145425d76503a02dcd3e6ddad59d9496273707..e7843412a322591b87789205f0dbae2ce4de27dc 100644 --- a/src/perf-tests/chunked-array-vs-native.ts +++ b/src/perf-tests/chunked-array-vs-native.ts @@ -1,5 +1,5 @@ import * as B from 'benchmark' -import ChunkedArray from 'mol-data/util/chunked-array' +import { ChunkedArray } from 'mol-data/util' function testNative(size: number) { const xs = new Array(size); diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 699c9037cafc14c7502954b09df33cee123854d4..5f7d33928912ffa1cdefcc86202c614415e353d7 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -2,84 +2,84 @@ import * as B from 'benchmark' import { Tuple, Segmentation, OrderedSet as OrdSet } from 'mol-data/int' import { AtomSet } from 'mol-model/structure' -export namespace Iteration { - const U = 1000, V = 2500; - - const control: number[] = []; - const sets = Object.create(null); - for (let i = 1; i < U; i++) { - const set = []; - for (let j = 1; j < V; j++) { - control[control.length] = j * j + 1; - set[set.length] = j * j + 1; - } - sets[i * i] = OrdSet.ofSortedArray(set); - } - const ms = AtomSet.create(sets); - - export function native() { - let s = 0; - for (let i = 0, _i = control.length; i < _i; i++) s += control[i]; - return s; - } - - export function iterators() { - let s = 0; - const it = AtomSet.atoms(ms); - while (it.hasNext) { - const v = it.move(); - s += Tuple.snd(v); - } - return s; - } - - export function elementAt() { - let s = 0; - for (let i = 0, _i = AtomSet.atomCount(ms); i < _i; i++) s += Tuple.snd(AtomSet.atomGetAt(ms, i)); - return s; - } - - export function manual() { - let s = 0; - const keys = AtomSet.unitIds(ms); - for (let i = 0, _i = OrdSet.size(keys); i < _i; i++) { - const set = AtomSet.unitGetById(ms, OrdSet.getAt(keys, i)); - for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - s += OrdSet.getAt(set, j); - } - } - return s; - } - - export function manual1() { - let s = 0; - for (let i = 0, _i = AtomSet.unitCount(ms); i < _i; i++) { - const set = AtomSet.unitGetByIndex(ms, i); - for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - s += OrdSet.getAt(set, j); - } - } - return s; - } - - export function run() { - const suite = new B.Suite(); - - // const values: number[] = []; - // for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0; - - console.log(Iteration.native(), Iteration.iterators(), Iteration.elementAt(), Iteration.manual(), Iteration.manual1()); - - suite - .add('native', () => Iteration.native()) - .add('iterators', () => Iteration.iterators()) - .add('manual', () => Iteration.manual()) - .add('manual1', () => Iteration.manual1()) - .add('el at', () => Iteration.elementAt()) - .on('cycle', (e: any) => console.log(String(e.target))) - .run(); - } -} +// export namespace Iteration { +// const U = 1000, V = 2500; + +// const control: number[] = []; +// const sets = Object.create(null); +// for (let i = 1; i < U; i++) { +// const set = []; +// for (let j = 1; j < V; j++) { +// control[control.length] = j * j + 1; +// set[set.length] = j * j + 1; +// } +// sets[i * i] = OrdSet.ofSortedArray(set); +// } +// const ms = AtomSet.create(sets); + +// export function native() { +// let s = 0; +// for (let i = 0, _i = control.length; i < _i; i++) s += control[i]; +// return s; +// } + +// export function iterators() { +// let s = 0; +// const it = AtomSet.atoms(ms); +// while (it.hasNext) { +// const v = it.move(); +// s += Tuple.snd(v); +// } +// return s; +// } + +// export function elementAt() { +// let s = 0; +// for (let i = 0, _i = AtomSet.atomCount(ms); i < _i; i++) s += Tuple.snd(AtomSet.atomGetAt(ms, i)); +// return s; +// } + +// export function manual() { +// let s = 0; +// const keys = AtomSet.unitIds(ms); +// for (let i = 0, _i = OrdSet.size(keys); i < _i; i++) { +// const set = AtomSet.unitGetById(ms, OrdSet.getAt(keys, i)); +// for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { +// s += OrdSet.getAt(set, j); +// } +// } +// return s; +// } + +// export function manual1() { +// let s = 0; +// for (let i = 0, _i = AtomSet.unitCount(ms); i < _i; i++) { +// const set = AtomSet.unitGetByIndex(ms, i); +// for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { +// s += OrdSet.getAt(set, j); +// } +// } +// return s; +// } + +// export function run() { +// const suite = new B.Suite(); + +// // const values: number[] = []; +// // for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0; + +// console.log(Iteration.native(), Iteration.iterators(), Iteration.elementAt(), Iteration.manual(), Iteration.manual1()); + +// suite +// .add('native', () => Iteration.native()) +// .add('iterators', () => Iteration.iterators()) +// .add('manual', () => Iteration.manual()) +// .add('manual1', () => Iteration.manual1()) +// .add('el at', () => Iteration.elementAt()) +// .on('cycle', (e: any) => console.log(String(e.target))) +// .run(); +// } +// } export namespace Union { function createArray(n: number) { @@ -331,7 +331,120 @@ export namespace ObjectVsMap { } } -ObjectVsMap.run(); +export namespace IntVsStringIndices { + type WithKeys<K> = { keys: K[], data: { [key: number]: number } } + type MapWithKeys = { keys: number[], map: Map<number, number> } + + function createCacheKeys(n: number): WithKeys<number> { + const data = Object.create(null), keys = []; + data.__ = void 0; + delete data.__; + for (let i = 1; i <= n; i++) { + const k = i * (i + 1); + keys[keys.length] = k; + data[k] = i + 1; + } + return { data, keys }; + } + + function createMapKeys(n: number): MapWithKeys { + const map = new Map<number, number>(), keys = []; + for (let i = 1; i <= n; i++) { + const k = i * (i + 1); + keys[keys.length] = k; + map.set(k, i + 1); + } + return { map, keys }; + } + + export function createInt(n: number) { + const ret = Object.create(null); + ret.__ = void 0; + delete ret.__; + for (let i = 1; i <= n; i++) ret[i * (i + 1)] = i + 1; + return ret; + } + + export function createStr(n: number) { + const ret = Object.create(null); + ret.__ = void 0; + delete ret.__; + for (let i = 1; i <= n; i++) ret['' + (i * (i + 1))] = i + 1; + return ret; + } + + export function createMap(n: number) { + const map = new Map<number, number>(); + for (let i = 1; i <= n; i++) map.set(i * (i + 1), i + 1); + return map; + } + + export function sumInt(xs: { [key: number]: number }) { + let s = 0; + const keys = Object.keys(xs); + for (let i = 0, _i = keys.length; i < _i; i++) s += xs[+keys[i]]; + return s; + } + + export function sumStr(xs: { [key: string]: number }) { + let s = 0; + const keys = Object.keys(xs); + for (let i = 0, _i = keys.length; i < _i; i++) s += xs[keys[i]]; + return s; + } + + export function sumMap(map: Map<number, number>) { + let s = 0; + const values = map.keys(); + while (true) { + const { done, value } = values.next(); + if (done) break; + s += value; + } + return s; + } + + export function sumCached(xs: WithKeys<number>) { + let s = 0; + const keys = xs.keys, data = xs.data; + for (let i = 0, _i = keys.length; i < _i; i++) s += data[keys[i]]; + return s; + } + + export function sumKeyMap(xs: MapWithKeys) { + let s = 0; + const keys = xs.keys, map = xs.map; + for (let i = 0, _i = keys.length; i < _i; i++) s += map.get(keys[i])!; + return s; + } + + export function run() { + const N = 1000; + //const int = createInt(N); + const map = createMap(N); + //const str = createStr(N); + const keys = createCacheKeys(N); + const keyMap = createMapKeys(N); + console.log(sumMap(map), sumCached(keys), sumKeyMap(keyMap)); + new B.Suite() + //.add('c int', () => createInt(N)) + .add('q map', () => sumMap(map)) + .add('c map', () => createMap(N)) + .add('c mk', () => createMapKeys(N)) + //.add('c str', () => createStr(N)) + .add('c cc', () => createCacheKeys(N)) + //.add('q int', () => sumInt(int)) + .add('q mk', () => sumKeyMap(keyMap)) + //.add('q str', () => sumStr(str)) + .add('q cc', () => sumCached(keys)) + .on('cycle', (e: any) => console.log(String(e.target))) + .run(); + } +} + +IntVsStringIndices.run(); + +//ObjectVsMap.run(); //testSegments(); diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 156d441f8393095f4d3236f42e561262d88b7719..4cb8ce453da97a58ff2b63ead4de33a5302d4b71 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -10,8 +10,8 @@ import * as util from 'util' import * as fs from 'fs' import CIF from 'mol-io/reader/cif' -import { Structure, Model, Queries as Q, Atom, AtomSet, Selection } from 'mol-model/structure' -import { OrderedSet as OrdSet, Segmentation } from 'mol-data/int' +import { Structure, Model, Queries as Q, Atom, AtomGroup, AtomSet, Selection, Symmetry } from 'mol-model/structure' +import { Segmentation } from 'mol-data/int' import to_mmCIF from 'mol-model/structure/export/mmcif' @@ -29,6 +29,21 @@ async function readData(path: string) { } } +function *test() { + yield 10; + return 15; +} + +async function runIt<T>(itP: () => IterableIterator<T>) { + const it = itP(); + while (true) { + const { value, done } = it.next(); + if (done) return value; + } +} + +runIt(test).then(r => console.log('rerdasdasda', r)) + export async function readCIF(path: string) { console.time('readData'); const input = await readData(path) @@ -77,8 +92,9 @@ export namespace PropertyAccess { l.unit = units[unitIds[i]]; const set = AtomSet.unitGetByIndex(atoms, i); - for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - l.atom = OrdSet.getAt(set, j); + + for (let j = 0, _j = AtomGroup.size(set); j < _j; j++) { + l.atom = AtomGroup.getAt(set, j); s += p(l); } } @@ -99,20 +115,20 @@ export namespace PropertyAccess { l.unit = unit; const set = AtomSet.unitGetByIndex(atoms, i); - const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set); + const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.atoms); const residues = unit.hierarchy.residueSegments; while (chainsIt.hasNext) { cC++; const chainSegment = chainsIt.move(); - const residuesIt = Segmentation.transientSegments(residues, set, chainSegment); + const residuesIt = Segmentation.transientSegments(residues, set.atoms, chainSegment); while (residuesIt.hasNext) { rC++; const residueSegment = residuesIt.move(); // l.atom = OrdSet.getAt(set, residueSegment.start); // console.log(unit.hierarchy.residues.auth_comp_id.value(unit.residueIndex[l.atom]), l.atom, OrdSet.getAt(set, residueSegment.end)) for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { - l.atom = OrdSet.getAt(set, j); + l.atom = AtomGroup.getAt(set, j); vA++; s += p(l); } @@ -136,9 +152,9 @@ export namespace PropertyAccess { const unit = units[unitIds[i]]; l.unit = unit; const set = AtomSet.unitGetByIndex(atoms, i); - const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set); + const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set.atoms); while (residuesIt.hasNext) { - l.atom = OrdSet.getAt(set, residuesIt.move().start); + l.atom = AtomGroup.getAt(set, residuesIt.move().start); s += p(l); } } @@ -203,8 +219,8 @@ export namespace PropertyAccess { const set = AtomSet.unitGetByIndex(atoms, i); //const { residueIndex, chainIndex } = unit; const p = unit.conformation.atomId.value; - for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - const aI = OrdSet.getAt(set, j); + for (let j = 0, _j = AtomGroup.size(set); j < _j; j++) { + const aI = AtomGroup.getAt(set, j); s += p(aI); } } @@ -241,13 +257,29 @@ export namespace PropertyAccess { console.log(to_mmCIF('test', s)); } + export function testAssembly(id: string, s: Structure) { + console.time('assembly') + const a = Symmetry.buildAssembly(s, '1'); + console.timeEnd('assembly') + fs.writeFileSync(`e:/test/molstar/${id}_assembly.bcif`, to_mmCIF(id, a, true)); + console.log('exported'); + //write(a); + } + export async function run() { - const { structures, models, mmcif } = await readCIF('./examples/1cbs_full.bcif'); - //const { structures, models, mmcif } = await readCIF('e:/test/quick/3j3q_full.bcif'); + //const { structures, models, mmcif } = await readCIF('./examples/1cbs_full.bcif'); + const { structures, models } = await readCIF('e:/test/quick/3j3q_full.bcif'); //const { structures, models, mmcif } = await readCIF('e:/test/quick/1cbs_updated.cif'); + //const { structures, models/*, mmcif*/ } = await readCIF('e:/test/quick/5j7v_updated.cif'); + + //console.log(mmcif.pdbx_struct_oper_list.matrix.toArray()); + // console.log(mmcif.pdbx_struct_oper_list.vector.toArray()); + + // testAssembly('5j7v', structures[0]); + // throw ''; + + // console.log(models[0].symmetry.assemblies); - console.log(mmcif.pdbx_struct_oper_list.matrix.toArray()); - console.log(mmcif.pdbx_struct_oper_list.vector.toArray()); //const { structures, models } = await readCIF('e:/test/molstar/3j3q.bcif'); @@ -258,8 +290,8 @@ export namespace PropertyAccess { // return; - console.log(baseline(models[0])); - console.log(sumProperty(structures[0], l => l.unit.model.conformation.atomId.value(l.atom))); + console.log('bs', baseline(models[0])); + console.log('sp', sumProperty(structures[0], l => l.unit.model.conformation.atomId.value(l.atom))); console.log(sumPropertySegmented(structures[0], l => l.unit.model.conformation.atomId.value(l.atom))); //console.log(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.atom))); diff --git a/src/script.ts b/src/script.ts index 03c5d970ae0e3540eecf67b85967d1af0ecad3fa..de1047e64d48f55c7e5f77161d7cc068df61bbcc 100644 --- a/src/script.ts +++ b/src/script.ts @@ -148,13 +148,14 @@ async function runCIF(input: string | Uint8Array) { export async function _cif() { let path = `./examples/1grm_updated.cif`; // path = '../test/3j3q.cif' // lets have a relative path for big test files + // path = 'e:/test/quick/3j3q_updated.cif'; const input = await readFileAsync(path, 'utf8') console.log('------------------'); console.log('Text CIF:'); runCIF(input); path = `./examples/1cbs_full.bcif`; - // const path = 'c:/test/quick/3j3q.cif'; + const input2 = await readFileAsync(path) console.log('------------------'); console.log('BinaryCIF:');