From fe572f89d9bc12b1265618545215f3d7ba4f6a59 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Sat, 28 Oct 2017 18:54:41 +0200 Subject: [PATCH] refactoring column part 1 --- src/mol-base/collections/_spec/column.spec.ts | 34 ++++ src/mol-base/collections/column.1.ts | 191 ++++++++++++++++++ src/mol-base/collections/column.ts | 2 +- .../{ => integer}/_spec/interval.spec.ts | 2 +- .../{ => integer}/_spec/ordered-set.spec.ts | 4 +- .../{ => integer}/_spec/segmentation.spec.ts | 8 +- .../{ => integer}/_spec/sorted-array.spec.ts | 4 +- .../_spec/tuple.spec.ts} | 2 +- .../collections/integer/segmentation.ts | 3 +- src/mol-data/model.ts | 12 +- src/mol-data/model/builders/mmcif.ts | 4 +- src/mol-data/structure.ts | 1 + src/perf-tests/sets.ts | 2 +- 13 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 src/mol-base/collections/_spec/column.spec.ts create mode 100644 src/mol-base/collections/column.1.ts rename src/mol-base/collections/{ => integer}/_spec/interval.spec.ts (98%) rename src/mol-base/collections/{ => integer}/_spec/ordered-set.spec.ts (99%) rename src/mol-base/collections/{ => integer}/_spec/segmentation.spec.ts (86%) rename src/mol-base/collections/{ => integer}/_spec/sorted-array.spec.ts (95%) rename src/mol-base/collections/{_spec/int-tuple.spec.ts => integer/_spec/tuple.spec.ts} (92%) diff --git a/src/mol-base/collections/_spec/column.spec.ts b/src/mol-base/collections/_spec/column.spec.ts new file mode 100644 index 000000000..5449d7d8e --- /dev/null +++ b/src/mol-base/collections/_spec/column.spec.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Column, { isTypedArray } from '../column.1' + +describe('column', () => { + const cc = Column.ofConst(10, 2, Column.Type.int); + const arr = Column.ofArray({ array: [1, 2, 3, 4], type: Column.Type.int }); + const arrWindow = Column.window(arr, 1, 3); + + const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), type: Column.Type.int }); + const typedWindow = Column.window(typed, 1, 3); + + it('constant', () => { + expect(cc.rowCount).toBe(2); + expect(cc.value(0)).toBe(10); + }); + + it('arr', () => { + expect(arr.rowCount).toBe(4); + expect(arr.value(1)).toBe(2); + expect(arrWindow.value(0)).toBe(2); + expect(arrWindow.rowCount).toBe(2); + }); + + it('typed', () => { + expect(typedWindow.value(0)).toBe(2); + expect(typedWindow.rowCount).toBe(2); + expect(isTypedArray(typedWindow.toArray())).toBe(true); + }); +}) \ No newline at end of file diff --git a/src/mol-base/collections/column.1.ts b/src/mol-base/collections/column.1.ts new file mode 100644 index 000000000..d851a7c75 --- /dev/null +++ b/src/mol-base/collections/column.1.ts @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +interface Column<T> { + readonly '@type': Column.Type, + readonly '@array': ArrayLike<any> | undefined, + + readonly isDefined: boolean, + readonly rowCount: number, + value(row: number): T, + valueKind(row: number): Column.ValueKind, + toArray(params?: Column.ToArrayParams): ReadonlyArray<T>, + stringEquals(row: number, value: string): boolean, + areValuesEqual(rowA: number, rowB: number): boolean +} + +namespace Column { + export type Type = typeof Type.str | typeof Type.int | typeof Type.float | typeof Type.vector | typeof Type.matrix + + export namespace Type { + export const str = { '@type': '' as string, kind: 'str' as 'str' }; + export const int = { '@type': 0 as number, kind: 'int' as 'int' }; + export const float = { '@type': 0 as number, kind: 'float' as 'float' }; + export const vector = { '@type': [] as number[], kind: 'vector' as 'vector' }; + export const matrix = { '@type': [] as number[][], kind: 'matrix' as 'matrix' }; + } + + export interface ToArrayParams { + array?: { new(size: number): ArrayLike<number> }, + start?: number, + /** Last row (exclusive) */ + end?: number + } + + export interface ArraySpec<T extends Type> { + array: ArrayLike<T['@type']>, + type: T, + valueKind?: (row: number) => ValueKind + } + + export const enum ValueKind { Present, NotPresent, Unknown } + + export function Undefined<T extends Type>(rowCount: number, type: T): Column<T['@type']> { + return constColumn(type['@type'], rowCount, type, ValueKind.NotPresent); + } + + export function ofConst<T extends Type>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> { + return constColumn(v, rowCount, type, ValueKind.Present); + } + + export function ofArray<T extends Column.Type>(spec: Column.ArraySpec<T>): Column<T['@type']> { + return arrayColumn(spec); + } + + export function window<T>(column: Column<T>, start: number, end: number) { + return windowColumn(column, start, end); + } + + /** Makes the column backned by an array. Useful for columns that accessed often. */ + export function asArrayColumn<T>(c: Column<T>, array?: ToArrayParams['array']): Column<T> { + if (c['@array']) return c; + if (!c.isDefined) return Undefined(c.rowCount, c['@type']) as any as Column<T>; + return arrayColumn({ array: c.toArray({ array }), type: c['@type'] as any, valueKind: c.valueKind }); + } +} + +export default Column; + +function constColumn<T extends Column.Type>(v: T['@type'], rowCount: number, type: T, valueKind: Column.ValueKind): Column<T['@type']> { + const value: Column<T['@type']>['value'] = row => v; + return { + '@type': type, + '@array': void 0, + isDefined: valueKind === Column.ValueKind.Present, + rowCount, + value, + valueKind: row => valueKind, + toArray: params => { + const { array } = createArray(rowCount, params); + for (let i = 0, _i = array.length; i < _i; i++) array[i] = v; + return array; + }, + stringEquals: type.kind === 'str' + ? (row, value) => value === v + : type.kind === 'float' || type.kind === 'int' + ? (row, value) => +value === v + : (row, value) => false, + areValuesEqual: (rowA, rowB) => true + } +} + +function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['@type']> { + const rowCount = array.length; + const value: Column<T['@type']>['value'] = row => array[row]; + const isTyped = isTypedArray(array); + return { + '@type': type, + '@array': array, + isDefined: true, + rowCount, + value, + valueKind: valueKind ? valueKind : row => Column.ValueKind.Present, + toArray: isTyped + ? params => typedArrayWindow(array, params) as any as ReadonlyArray<T> + : params => { + const { start, end } = getArrayBounds(rowCount, params); + if (start === 0 && end === array.length) return array as ReadonlyArray<T['@type']>; + const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any; + for (let i = 0, _i = end - start; i < _i; i++) ret[i] = array[start + i]; + return ret; + }, + stringEquals: type.kind === 'int' || type.kind === 'float' + ? (row, value) => (array as any)[row] === +value + : type.kind === 'str' + ? (row, value) => array[row] === value + : (row, value) => false, + areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB] + } +} + +function windowColumn<T>(column: Column<T>, start: number, end: number) { + if (!column.isDefined) return Column.Undefined(end - start, column['@type']); + if (column['@array'] && isTypedArray(column['@array'])) return windowTyped(column, start, end); + return windowFull(column, start, end); +} + +function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> { + const array = typedArrayWindow(c['@array'], { start, end }); + return arrayColumn({ array, type: c['@type'], valueKind: c.valueKind }) as any; +} + +function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> { + const v = c.value, vk = c.valueKind, se = c.stringEquals, ave = c.areValuesEqual; + const value: Column<T>['value'] = start > 0 ? v : row => v(row + start); + const rowCount = end - start; + return { + '@type': c['@type'], + '@array': void 0, + isDefined: c.isDefined, + rowCount, + value, + valueKind: start > 0 ? vk : row => vk(row + start), + toArray: params => { + const { array } = createArray(rowCount, params); + for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start); + return array; + }, + stringEquals: start > 0 ? se : (row, value) => se(row + start, value), + areValuesEqual: start > 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start) + }; +} + +/** A helped function for Column.toArray */ +export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams) { + const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0; + const end = params && typeof params.end !== 'undefined' ? Math.min(params.end, rowCount) : rowCount; + return { start, end }; +} + +/** A helped function for Column.toArray */ +export function createArray(rowCount: number, params?: Column.ToArrayParams) { + const c = params && typeof params.array !== 'undefined' ? params.array : Array; + const { start, end } = getArrayBounds(rowCount, params); + return { array: new c(end - start) as any[], start, end }; +} + +/** A helped function for Column.toArray */ +export function fillArrayValues(value: (row: number) => any, target: any[], start: number) { + for (let i = 0, _e = target.length; i < _e; i++) target[i] = value(start + i); + return target; +} + +/** A helped function for Column.toArray */ +export function createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams) { + const { array, start } = createArray(rowCount, params); + return fillArrayValues(value, array, start); +} + +export function isTypedArray(data: any): boolean { + return !!data.buffer && typeof data.byteLength === 'number' && typeof data.BYTES_PER_ELEMENT === 'number'; +} + +export function typedArrayWindow(data: any, params?: Column.ToArrayParams): ReadonlyArray<number> { + const { constructor, buffer, length, byteOffset, BYTES_PER_ELEMENT } = data; + const { start, end } = getArrayBounds(length, params); + if (start === 0 && end === length) return data; + return new constructor(buffer, byteOffset + BYTES_PER_ELEMENT * start, Math.min(length, end - start)); +} \ No newline at end of file diff --git a/src/mol-base/collections/column.ts b/src/mol-base/collections/column.ts index 0a9ec96f6..4aec52f9e 100644 --- a/src/mol-base/collections/column.ts +++ b/src/mol-base/collections/column.ts @@ -53,7 +53,7 @@ export function UndefinedColumn<T extends ColumnType>(rowCount: number, type: T) } } -export function SingleValueColumn<T extends ColumnType>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> { +export function ConstColumn<T extends ColumnType>(v: T['@type'], rowCount: number, type: T): Column<T['@type']> { const value: Column<T['@type']>['value'] = row => v; return { '@type': type, diff --git a/src/mol-base/collections/_spec/interval.spec.ts b/src/mol-base/collections/integer/_spec/interval.spec.ts similarity index 98% rename from src/mol-base/collections/_spec/interval.spec.ts rename to src/mol-base/collections/integer/_spec/interval.spec.ts index 015d54123..cda22df8e 100644 --- a/src/mol-base/collections/_spec/interval.spec.ts +++ b/src/mol-base/collections/integer/_spec/interval.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Interval from '../integer/interval' +import Interval from '../interval' describe('interval', () => { function testI(name: string, a: Interval, b: Interval) { diff --git a/src/mol-base/collections/_spec/ordered-set.spec.ts b/src/mol-base/collections/integer/_spec/ordered-set.spec.ts similarity index 99% rename from src/mol-base/collections/_spec/ordered-set.spec.ts rename to src/mol-base/collections/integer/_spec/ordered-set.spec.ts index 28927b185..41b70f708 100644 --- a/src/mol-base/collections/_spec/ordered-set.spec.ts +++ b/src/mol-base/collections/integer/_spec/ordered-set.spec.ts @@ -4,8 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import OrderedSet from '../integer/ordered-set' -import Interval from '../integer/interval' +import OrderedSet from '../ordered-set' +import Interval from '../interval' describe('ordered set', () => { function ordSetToArray(set: OrderedSet) { diff --git a/src/mol-base/collections/_spec/segmentation.spec.ts b/src/mol-base/collections/integer/_spec/segmentation.spec.ts similarity index 86% rename from src/mol-base/collections/_spec/segmentation.spec.ts rename to src/mol-base/collections/integer/_spec/segmentation.spec.ts index b44766e5f..c77fbb86f 100644 --- a/src/mol-base/collections/_spec/segmentation.spec.ts +++ b/src/mol-base/collections/integer/_spec/segmentation.spec.ts @@ -4,9 +4,9 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import OrderedSet from '../integer/ordered-set' -import Interval from '../integer/interval' -import Segmentation from '../integer/segmentation' +import OrderedSet from '../ordered-set' +import Interval from '../interval' +import Segmentation from '../segmentation' describe('segments', () => { const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); @@ -27,7 +27,7 @@ describe('segments', () => { }) it('iteration', () => { - const it = Segmentation.segments(segs, data); + const it = Segmentation.transientSegments(segs, data); const t = Object.create(null); while (it.hasNext) { diff --git a/src/mol-base/collections/_spec/sorted-array.spec.ts b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts similarity index 95% rename from src/mol-base/collections/_spec/sorted-array.spec.ts rename to src/mol-base/collections/integer/_spec/sorted-array.spec.ts index 7aa731c9d..e3b729523 100644 --- a/src/mol-base/collections/_spec/sorted-array.spec.ts +++ b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts @@ -4,8 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Interval from '../integer/interval' -import SortedArray from '../integer/sorted-array' +import Interval from '../interval' +import SortedArray from '../sorted-array' describe('sortedArray', () => { function testI(name: string, a: Interval, b: Interval) { diff --git a/src/mol-base/collections/_spec/int-tuple.spec.ts b/src/mol-base/collections/integer/_spec/tuple.spec.ts similarity index 92% rename from src/mol-base/collections/_spec/int-tuple.spec.ts rename to src/mol-base/collections/integer/_spec/tuple.spec.ts index 6ff7827c4..45b02a0ef 100644 --- a/src/mol-base/collections/_spec/int-tuple.spec.ts +++ b/src/mol-base/collections/integer/_spec/tuple.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import IntTuple from '../integer/tuple' +import IntTuple from '../tuple' describe('int pair', () => { it('works', () => { diff --git a/src/mol-base/collections/integer/segmentation.ts b/src/mol-base/collections/integer/segmentation.ts index 6ecdbdf27..dda6b63a3 100644 --- a/src/mol-base/collections/integer/segmentation.ts +++ b/src/mol-base/collections/integer/segmentation.ts @@ -18,7 +18,8 @@ namespace Segmentation { export const getSegment: (segs: Segmentation, value: number) => number = Impl.getSegment as any; export const projectValue: (segs: Segmentation, set: OrderedSet, value: number) => Interval = Impl.projectValue as any; - export const segments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any; + // Segment iterator that mutates a single segment object to mark all the segments. + export const transientSegments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any; } interface Segmentation { diff --git a/src/mol-data/model.ts b/src/mol-data/model.ts index 54f89ceb9..689b170e5 100644 --- a/src/mol-data/model.ts +++ b/src/mol-data/model.ts @@ -14,16 +14,18 @@ import Segmentation from '../mol-base/collections/integer/segmentation' * * "Atoms" are integers in the range [0, atomCount). */ -interface Model { +interface Model extends Readonly<{ sourceData: Formats.RawData, common: CommonInterface, macromolecule: MacromoleculeInterface atomCount: number, - chains: Segmentation, - residues: Segmentation, - entities: Segmentation -} + segments: Readonly<{ + chains: Segmentation, + residues: Segmentation, + entities: Segmentation + }> +}> { } export default Model \ No newline at end of file diff --git a/src/mol-data/model/builders/mmcif.ts b/src/mol-data/model/builders/mmcif.ts index 71eff5052..f2318b794 100644 --- a/src/mol-data/model/builders/mmcif.ts +++ b/src/mol-data/model/builders/mmcif.ts @@ -57,9 +57,7 @@ function createModel(raw: RawData, data: mmCIF, bounds: Interval): Model { common: 0 as any, macromolecule: 0 as any, atomCount: Interval.size(bounds), - residues: segments.residues, - chains: segments.chains, - entities: segments.entities + segments }; } diff --git a/src/mol-data/structure.ts b/src/mol-data/structure.ts index 26059084e..d4488fca5 100644 --- a/src/mol-data/structure.ts +++ b/src/mol-data/structure.ts @@ -40,6 +40,7 @@ export interface Unit extends Readonly<{ getPosition(atom: number, slot: Vec3): Vec3 } +// TODO: do "single model" version of the structure? export interface Structure extends Readonly<{ units: Readonly<{ [id: number]: Unit }>, atoms: AtomSet diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 4794a55cf..f5d4f394c 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -274,7 +274,7 @@ export namespace Tuples { export function testSegments() { const data = OrdSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); const segs = Segmentation.create([0, 4, 10, 12, 13, 15, 25]); - const it = Segmentation.segments(segs, data); + const it = Segmentation.transientSegments(segs, data); while (it.hasNext) { const s = it.move(); -- GitLab