diff --git a/src/mol-data/db/_spec/table.spec.ts b/src/mol-data/db/_spec/table.spec.ts index 76aae4de0157850c336db0d09d769780b546db9c..0691665197e6d4132d9e7e89ff9dab8dc73cafd1 100644 --- a/src/mol-data/db/_spec/table.spec.ts +++ b/src/mol-data/db/_spec/table.spec.ts @@ -12,8 +12,8 @@ import Table from '../table' describe('column', () => { const cc = Column.ofConst(10, 2, Column.Schema.int); const arr = Column.ofArray({ array: [1, 2, 3, 4], schema: Column.Schema.int }); - const arrNumberList = Column.ofArray({ array: [[1, 2], [3, 4], [5, 6]], schema: Column.Schema.List<number>() }); - const arrStringList = Column.ofArray({ array: [['a', 'b'], ['c', 'd'], ['e', 'f']], schema: Column.Schema.List<string>() }); + const arrNumberList = Column.ofArray({ array: [[1, 2], [3, 4], [5, 6]], schema: Column.Schema.List(' ', x => parseInt(x, 10)) }); + const arrStringList = Column.ofArray({ array: [['a', 'b'], ['c', 'd'], ['e', 'f']], schema: Column.Schema.List(',', x => x) }); const arrWindow = Column.window(arr, 1, 3); const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), schema: Column.Schema.int }); diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index db692ef06293e8839b5ead8703f7f764b8c2af4e..af668d4f58bf260858546ef29fffd80e4965e421 100644 --- a/src/mol-data/db/column.ts +++ b/src/mol-data/db/column.ts @@ -36,7 +36,7 @@ namespace Column { export type Tensor = { '@type': 'tensor', T: Tensors, space: Tensors.Space } & Base<'tensor'> export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'> - export type List<T extends number|string> = { '@type': 'list', T: T[] } & Base<'list'> + export type List<T extends number|string> = { '@type': 'list', T: T[], separator: string, itemParse: (x: string) => T } & Base<'list'> export const str: Str = { '@type': 'str', T: '', valueType: 'str' }; export const int: Int = { '@type': 'int', T: 0, valueType: 'int' }; @@ -54,8 +54,8 @@ namespace Column { if (typeof defaultValue !== 'undefined') return { ...t, T: defaultValue } as any as Aliased<T>; return t as any as Aliased<T>; } - export function List<T extends number|string>(defaultValue: T[] = []): List<T> { - return { '@type': 'list', T: defaultValue, valueType: 'list' } + export function List<T extends number|string>(separator: string, itemParse: (x: string) => T, defaultValue: T[] = []): List<T> { + return { '@type': 'list', T: defaultValue, separator, itemParse, valueType: 'list' } } } diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts index 90fae982aebd67108262b6672414c6c257c0c593..e7d619f280a0d66f6eead466e4b9163c7363b947 100644 --- a/src/mol-io/reader/_spec/cif.spec.ts +++ b/src/mol-io/reader/_spec/cif.spec.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import * as Data from '../cif/data-model' @@ -9,33 +10,45 @@ import TextField from '../cif/text/field' import * as Schema from '../cif/schema' import { Column } from 'mol-data/db' -const columnData = `123abc`; +const columnData = `123abc d,e,f '4 5 6'`; +// 123abc d,e,f '4 5 6' const intField = TextField({ data: columnData, indices: [0, 1, 1, 2, 2, 3], count: 3 }, 3); const strField = TextField({ data: columnData, indices: [3, 4, 4, 5, 5, 6], count: 3 }, 3); +const strListField = TextField({ data: columnData, indices: [7, 12], count: 1 }, 1); +const intListField = TextField({ data: columnData, indices: [14, 19], count: 1 }, 1); -const testBlock = Data.Block(['atoms'], { - atoms: Data.Category('atoms', 3, ['x', 'name'], { - x: intField, - name: strField +const testBlock = Data.Block(['test'], { + test: Data.Category('test', 3, ['int', 'str', 'strList', 'intList'], { + int: intField, + str: strField, + strList: strListField, + intList: intListField }) }, 'test'); namespace TestSchema { - export const atoms = { x: Column.Schema.int, name: Column.Schema.str } - export const schema = { atoms } + export const test = { + int: Column.Schema.int, + str: Column.Schema.str, + strList: Column.Schema.List(',', x => x), + intList: Column.Schema.List(' ', x => parseInt(x, 10)) + } + export const schema = { test } } describe('schema', () => { const db = Schema.toDatabase(TestSchema.schema, testBlock); it('property access', () => { - const { x, name } = db.atoms; - expect(x.value(0)).toBe(1); - expect(name.value(1)).toBe('b'); + const { int, str, strList, intList } = db.test; + expect(int.value(0)).toBe(1); + expect(str.value(1)).toBe('b'); + expect(strList.value(0)).toEqual(['d', 'e', 'f']); + expect(intList.value(0)).toEqual([4, 5, 6]); }); it('toArray', () => { - const ret = db.atoms.x.toArray({ array: Int32Array }); + const ret = db.test.int.toArray({ array: Int32Array }); expect(ret.length).toBe(3); expect(ret[0]).toBe(1); expect(ret[1]).toBe(2); diff --git a/src/mol-io/reader/cif/binary/field.ts b/src/mol-io/reader/cif/binary/field.ts index 854eaa550003539498fcaf4d545ead42f536cb21..dd5758f519c895f28ea88801cc627692cb29cd42 100644 --- a/src/mol-io/reader/cif/binary/field.ts +++ b/src/mol-io/reader/cif/binary/field.ts @@ -31,10 +31,6 @@ export default function Field(column: EncodedColumn): Data.Field { ? row => data[row] : row => { const v = data[row]; return fastParseFloat(v, 0, v.length); }; - const list: Data.Field['list'] = mask - ? row => mask[row] === Column.ValueKind.Present ? data[row] : [] - : row => data[row]; - const valueKind: Data.Field['valueKind'] = mask ? row => mask[row] : row => Column.ValueKind.Present; @@ -48,7 +44,6 @@ export default function Field(column: EncodedColumn): Data.Field { str, int, float, - list, valueKind, areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB], toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), @@ -57,7 +52,6 @@ export default function Field(column: EncodedColumn): Data.Field { : params => ColumnHelpers.createAndFillArray(rowCount, int, params), toFloatArray: isNumeric ? params => ColumnHelpers.typedArrayWindow(data, params) - : params => ColumnHelpers.createAndFillArray(rowCount, float, params), - toListArray: params => ColumnHelpers.createAndFillArray(rowCount, list, params) + : params => ColumnHelpers.createAndFillArray(rowCount, float, params) }; } \ No newline at end of file diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index 74a15117a134757bb6e747a278445463b01c8e04..feb812fa368a40520afcb76825642cd756705ef5 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -68,7 +68,6 @@ export interface Field { str(row: number): string, int(row: number): number, float(row: number): number, - list<T extends number|string>(row: number): T[], valueKind(row: number): Column.ValueKind, @@ -77,7 +76,6 @@ export interface Field { toStringArray(params?: Column.ToArrayParams<string>): ReadonlyArray<string>, toIntArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number>, toFloatArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number> - toListArray<T extends number|string>(params?: Column.ToArrayParams<T[]>): ReadonlyArray<T[]> } export function getTensor(category: Category, field: string, space: Tensor.Space, row: number): Tensor { diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index 707710930e283a417ac2da38e1b49f02d2c98701..973c16d27bcfb8a68d9fc4e8d89bb93ca3bd30ad 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/src/mol-io/reader/cif/schema.ts @@ -7,6 +7,7 @@ import { Database, Table, Column, ColumnHelpers } from 'mol-data/db' import { Tensor } from 'mol-math/linear-algebra' +import { arrayEqual } from 'mol-util' import * as Data from './data-model' export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.Frame): Frame { @@ -24,7 +25,7 @@ function getColumnCtor(t: Column.Schema): ColumnCtor { case 'str': return (f, c, k) => createColumn(t, f, f.str, f.toStringArray); case 'int': return (f, c, k) => createColumn(t, f, f.int, f.toIntArray); case 'float': return (f, c, k) => createColumn(t, f, f.float, f.toFloatArray); - case 'list': return (f, c, k) => createColumn(t, f, f.list, f.toListArray); + case 'list': throw new Error('Use createListColumn instead.'); case 'tensor': throw new Error('Use createTensorColumn instead.'); } } @@ -42,6 +43,26 @@ function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: }; } +function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.Category, key: string): Column<(number|string)[]> { + const separator = schema.separator; + const itemParse = schema.itemParse; + + const f = category.getField(key); + const value = f ? (row: number) => f.str(row).split(separator).map(x => itemParse(x.trim())).filter(x => !!x) : (row: number) => [] + const toArray: Column<T[]>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params) + + return { + schema, + '@array': void 0, + isDefined: !!f, + rowCount: category.rowCount, + value, + valueKind: f ? f.valueKind : () => Column.ValueKind.NotPresent, + areValuesEqual: (rowA, rowB) => arrayEqual(value(rowA), value(rowB)), + toArray + }; +} + function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor> { const space = schema.space; let firstFieldName: string; @@ -84,7 +105,9 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name get: function() { if (cache[k]) return cache[k]; const fType = schema[k]; - if (fType.valueType === 'tensor') { + if (fType.valueType === 'list') { + cache[k] = createListColumn(fType, category, k); + } else if (fType.valueType === 'tensor') { cache[k] = createTensorColumn(fType, category, k); } else { const ctor = getColumnCtor(fType); diff --git a/src/mol-io/reader/cif/text/field.ts b/src/mol-io/reader/cif/text/field.ts index a1c929cba05c6c2bdc2a4a253cbe82bd3c616f1b..8b6daa2b88a07aa7adc1d4eedbf8d0c83234b394 100644 --- a/src/mol-io/reader/cif/text/field.ts +++ b/src/mol-io/reader/cif/text/field.ts @@ -28,12 +28,6 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Fie return fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0; }; - const list: Data.Field['list'] = <T extends string|number>(row: number) => { - const ret = data.substring(indices[2 * row], indices[2 * row + 1]); - if (ret === '.' || ret === '?') return []; - return ret.split(',') as T[]; - }; - const valueKind: Data.Field['valueKind'] = row => { const s = indices[2 * row]; if (indices[2 * row + 1] - s !== 1) return Column.ValueKind.Present; @@ -50,12 +44,10 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Fie str, int, float, - list, valueKind, areValuesEqual: TokenColumn.areValuesEqualProvider(tokens), toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), - toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params), - toListArray: params => ColumnHelpers.createAndFillArray(rowCount, list, params) + toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) } } \ No newline at end of file diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index 1f08e032009d55bf6590c778617169d2caa4d6b9..9bf21af471c8c4da3d76688975e1e0875a5ae657 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/src/mol-io/writer/cif/encoder.ts @@ -80,6 +80,10 @@ function columnValue(k: string) { return (i: number, d: any) => d[k].value(i); } +function columnListValue(k: string) { + return (i: number, d: any) => d[k].value(i).join(d[k].schema.separator); +} + function columnTensorValue(k: string, ...coords: number[]) { return (i: number, d: any) => d[k].schema.space.get(d[k].value(i), ...coords); } @@ -134,7 +138,7 @@ export namespace FieldDefinitions { } else if (t.valueType === 'str') { fields.push({ name: k, type: FieldType.Str, value: columnValue(k), valueKind: columnValueKind(k) }); } else if (t.valueType === 'list') { - throw new Error('list not implemented'); + fields.push({ name: k, type: FieldType.Str, value: columnListValue(k), valueKind: columnValueKind(k) }) } else if (t.valueType === 'tensor') { fields.push(...getTensorDefinitions(k, t.space)) } else { diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts index 35bbb7d650f398f371d905890c1ffbbb5fce55fa..5d83ca871cbae1575343706704d6c5ced8adff76 100644 --- a/src/mol-model/structure/model/formats/mmcif/assembly.ts +++ b/src/mol-model/structure/model/formats/mmcif/assembly.ts @@ -40,7 +40,7 @@ function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices) 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) + asymIds: asym_id_list.value(i) }; } diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 18a0aa3cd809aab11fc83dfe015e3d99cf98c3f8..191fddd923380527a885f937f5a2cdc8859374c0 100644 --- a/src/mol-util/index.ts +++ b/src/mol-util/index.ts @@ -2,6 +2,7 @@ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import BitFlags from './bit-flags' @@ -11,4 +12,15 @@ import StringBuilder from './string-builder' import Time from './time' import UUID from './uuid' -export { BitFlags, Computation, Scheduler, StringBuilder, Time, UUID } \ No newline at end of file +export { BitFlags, Computation, Scheduler, StringBuilder, Time, UUID } + +export function arrayEqual<T>(arr1: T[], arr2: T[]) { + const length = arr1.length + if (length !== arr2.length) return false + for (let i = 0; i < length; i++) { + if (arr1[i] !== arr2[i]) { + return false + } + } + return true +} \ No newline at end of file