Skip to content
Snippets Groups Projects
Commit 00d1e1c5 authored by Alexander Rose's avatar Alexander Rose
Browse files

improving & testing list column type

parent 3add0d06
Branches
Tags
No related merge requests found
...@@ -12,8 +12,8 @@ import Table from '../table' ...@@ -12,8 +12,8 @@ import Table from '../table'
describe('column', () => { describe('column', () => {
const cc = Column.ofConst(10, 2, Column.Schema.int); const cc = Column.ofConst(10, 2, Column.Schema.int);
const arr = Column.ofArray({ array: [1, 2, 3, 4], schema: 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 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<string>() }); 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 arrWindow = Column.window(arr, 1, 3);
const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), schema: Column.Schema.int }); const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), schema: Column.Schema.int });
......
...@@ -36,7 +36,7 @@ namespace Column { ...@@ -36,7 +36,7 @@ namespace Column {
export type Tensor = { '@type': 'tensor', T: Tensors, space: Tensors.Space } & Base<'tensor'> 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 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 str: Str = { '@type': 'str', T: '', valueType: 'str' };
export const int: Int = { '@type': 'int', T: 0, valueType: 'int' }; export const int: Int = { '@type': 'int', T: 0, valueType: 'int' };
...@@ -54,8 +54,8 @@ namespace Column { ...@@ -54,8 +54,8 @@ namespace Column {
if (typeof defaultValue !== 'undefined') return { ...t, T: defaultValue } as any as Aliased<T>; if (typeof defaultValue !== 'undefined') return { ...t, T: defaultValue } as any as Aliased<T>;
return t as any as Aliased<T>; return t as any as Aliased<T>;
} }
export function List<T extends number|string>(defaultValue: T[] = []): List<T> { export function List<T extends number|string>(separator: string, itemParse: (x: string) => T, defaultValue: T[] = []): List<T> {
return { '@type': 'list', T: defaultValue, valueType: 'list' } return { '@type': 'list', T: defaultValue, separator, itemParse, valueType: 'list' }
} }
} }
......
/** /**
* 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 David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import * as Data from '../cif/data-model' import * as Data from '../cif/data-model'
...@@ -9,33 +10,45 @@ import TextField from '../cif/text/field' ...@@ -9,33 +10,45 @@ import TextField from '../cif/text/field'
import * as Schema from '../cif/schema' import * as Schema from '../cif/schema'
import { Column } from 'mol-data/db' 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 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 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'], { const testBlock = Data.Block(['test'], {
atoms: Data.Category('atoms', 3, ['x', 'name'], { test: Data.Category('test', 3, ['int', 'str', 'strList', 'intList'], {
x: intField, int: intField,
name: strField str: strField,
strList: strListField,
intList: intListField
}) })
}, 'test'); }, 'test');
namespace TestSchema { namespace TestSchema {
export const atoms = { x: Column.Schema.int, name: Column.Schema.str } export const test = {
export const schema = { atoms } 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', () => { describe('schema', () => {
const db = Schema.toDatabase(TestSchema.schema, testBlock); const db = Schema.toDatabase(TestSchema.schema, testBlock);
it('property access', () => { it('property access', () => {
const { x, name } = db.atoms; const { int, str, strList, intList } = db.test;
expect(x.value(0)).toBe(1); expect(int.value(0)).toBe(1);
expect(name.value(1)).toBe('b'); expect(str.value(1)).toBe('b');
expect(strList.value(0)).toEqual(['d', 'e', 'f']);
expect(intList.value(0)).toEqual([4, 5, 6]);
}); });
it('toArray', () => { 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.length).toBe(3);
expect(ret[0]).toBe(1); expect(ret[0]).toBe(1);
expect(ret[1]).toBe(2); expect(ret[1]).toBe(2);
......
...@@ -31,10 +31,6 @@ export default function Field(column: EncodedColumn): Data.Field { ...@@ -31,10 +31,6 @@ export default function Field(column: EncodedColumn): Data.Field {
? row => data[row] ? row => data[row]
: row => { const v = data[row]; return fastParseFloat(v, 0, v.length); }; : 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 const valueKind: Data.Field['valueKind'] = mask
? row => mask[row] ? row => mask[row]
: row => Column.ValueKind.Present; : row => Column.ValueKind.Present;
...@@ -48,7 +44,6 @@ export default function Field(column: EncodedColumn): Data.Field { ...@@ -48,7 +44,6 @@ export default function Field(column: EncodedColumn): Data.Field {
str, str,
int, int,
float, float,
list,
valueKind, valueKind,
areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB], areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB],
toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params),
...@@ -57,7 +52,6 @@ export default function Field(column: EncodedColumn): Data.Field { ...@@ -57,7 +52,6 @@ export default function Field(column: EncodedColumn): Data.Field {
: params => ColumnHelpers.createAndFillArray(rowCount, int, params), : params => ColumnHelpers.createAndFillArray(rowCount, int, params),
toFloatArray: isNumeric toFloatArray: isNumeric
? params => ColumnHelpers.typedArrayWindow(data, params) ? params => ColumnHelpers.typedArrayWindow(data, params)
: params => ColumnHelpers.createAndFillArray(rowCount, float, params), : params => ColumnHelpers.createAndFillArray(rowCount, float, params)
toListArray: params => ColumnHelpers.createAndFillArray(rowCount, list, params)
}; };
} }
\ No newline at end of file
...@@ -68,7 +68,6 @@ export interface Field { ...@@ -68,7 +68,6 @@ export interface Field {
str(row: number): string, str(row: number): string,
int(row: number): number, int(row: number): number,
float(row: number): number, float(row: number): number,
list<T extends number|string>(row: number): T[],
valueKind(row: number): Column.ValueKind, valueKind(row: number): Column.ValueKind,
...@@ -77,7 +76,6 @@ export interface Field { ...@@ -77,7 +76,6 @@ export interface Field {
toStringArray(params?: Column.ToArrayParams<string>): ReadonlyArray<string>, toStringArray(params?: Column.ToArrayParams<string>): ReadonlyArray<string>,
toIntArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number>, toIntArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number>,
toFloatArray(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 { export function getTensor(category: Category, field: string, space: Tensor.Space, row: number): Tensor {
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import { Database, Table, Column, ColumnHelpers } from 'mol-data/db' import { Database, Table, Column, ColumnHelpers } from 'mol-data/db'
import { Tensor } from 'mol-math/linear-algebra' import { Tensor } from 'mol-math/linear-algebra'
import { arrayEqual } from 'mol-util'
import * as Data from './data-model' 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 { 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 { ...@@ -24,7 +25,7 @@ function getColumnCtor(t: Column.Schema): ColumnCtor {
case 'str': return (f, c, k) => createColumn(t, f, f.str, f.toStringArray); 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 '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 '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.'); case 'tensor': throw new Error('Use createTensorColumn instead.');
} }
} }
...@@ -42,6 +43,26 @@ function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: ...@@ -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> { function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor> {
const space = schema.space; const space = schema.space;
let firstFieldName: string; let firstFieldName: string;
...@@ -84,7 +105,9 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name ...@@ -84,7 +105,9 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name
get: function() { get: function() {
if (cache[k]) return cache[k]; if (cache[k]) return cache[k];
const fType = schema[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); cache[k] = createTensorColumn(fType, category, k);
} else { } else {
const ctor = getColumnCtor(fType); const ctor = getColumnCtor(fType);
......
...@@ -28,12 +28,6 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Fie ...@@ -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; 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 valueKind: Data.Field['valueKind'] = row => {
const s = indices[2 * row]; const s = indices[2 * row];
if (indices[2 * row + 1] - s !== 1) return Column.ValueKind.Present; 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 ...@@ -50,12 +44,10 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Fie
str, str,
int, int,
float, float,
list,
valueKind, valueKind,
areValuesEqual: TokenColumn.areValuesEqualProvider(tokens), areValuesEqual: TokenColumn.areValuesEqualProvider(tokens),
toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params),
toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params),
toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params), toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params)
toListArray: params => ColumnHelpers.createAndFillArray(rowCount, list, params)
} }
} }
\ No newline at end of file
...@@ -80,6 +80,10 @@ function columnValue(k: string) { ...@@ -80,6 +80,10 @@ function columnValue(k: string) {
return (i: number, d: any) => d[k].value(i); 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[]) { function columnTensorValue(k: string, ...coords: number[]) {
return (i: number, d: any) => d[k].schema.space.get(d[k].value(i), ...coords); return (i: number, d: any) => d[k].schema.space.get(d[k].value(i), ...coords);
} }
...@@ -134,7 +138,7 @@ export namespace FieldDefinitions { ...@@ -134,7 +138,7 @@ export namespace FieldDefinitions {
} else if (t.valueType === 'str') { } else if (t.valueType === 'str') {
fields.push({ name: k, type: FieldType.Str, value: columnValue(k), valueKind: columnValueKind(k) }); fields.push({ name: k, type: FieldType.Str, value: columnValue(k), valueKind: columnValueKind(k) });
} else if (t.valueType === 'list') { } 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') { } else if (t.valueType === 'tensor') {
fields.push(...getTensorDefinitions(k, t.space)) fields.push(...getTensorDefinitions(k, t.space))
} else { } else {
......
...@@ -40,7 +40,7 @@ function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices) ...@@ -40,7 +40,7 @@ function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices)
if (assembly_id.value(i) !== id) continue; if (assembly_id.value(i) !== id) continue;
generators[generators.length] = { generators[generators.length] = {
expression: oper_expression.value(i), 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)
}; };
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* *
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import BitFlags from './bit-flags' import BitFlags from './bit-flags'
...@@ -11,4 +12,15 @@ import StringBuilder from './string-builder' ...@@ -11,4 +12,15 @@ import StringBuilder from './string-builder'
import Time from './time' import Time from './time'
import UUID from './uuid' import UUID from './uuid'
export { BitFlags, Computation, Scheduler, StringBuilder, Time, UUID } export { BitFlags, Computation, Scheduler, StringBuilder, Time, UUID }
\ No newline at end of file
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment