diff --git a/src/mol-base/collections/_spec/table.spec.ts b/src/mol-base/collections/_spec/table.spec.ts index a41ce9f115f998ce3197f9f11ed0b696443a5b12..35eaacb85e3695341f292e91f6c2444063380ac5 100644 --- a/src/mol-base/collections/_spec/table.spec.ts +++ b/src/mol-base/collections/_spec/table.spec.ts @@ -69,6 +69,27 @@ describe('table', () => { expect(t.n.toArray()).toEqual(['row1', 'row2']); }); + it('ofArrays', () => { + const t = Table.ofArrays<typeof schema>(schema, { + x: [10, -1], + n: ['row1', 'row2'], + }); + expect(t.x.toArray()).toEqual([10, -1]); + expect(t.n.toArray()).toEqual(['row1', 'row2']); + }); + + it('pickColumns', () => { + const t = Table.ofColumns<typeof schema>({ + x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), + n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }), + }); + const s = { x: Column.Type.int }; + const picked = Table.pickColumns(s, t); + expect(picked._columns).toEqual(['x']); + expect(picked._rowCount).toEqual(2); + expect(picked.x.toArray()).toEqual([10, -1]); + }); + it('sort', () => { const t = Table.ofColumns<typeof schema>({ x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), diff --git a/src/mol-base/collections/column.ts b/src/mol-base/collections/column.ts index 4650e8a3739bc0fe6a4a24e5e17763ab89f767bb..4ae3ad6f6179aa7cd6b11de1997640a05292a77b 100644 --- a/src/mol-base/collections/column.ts +++ b/src/mol-base/collections/column.ts @@ -17,18 +17,24 @@ interface Column<T> { } namespace Column { - export type Type = typeof Type.str | typeof Type.int | typeof Type.float | Type.Vector | Type.Matrix + export type Type<T = any> = Type.Str | Type.Int | Type.Float | Type.Vector | Type.Matrix | Type.Aliased<T> export namespace Type { - export const str = { T: '' as string, kind: 'str' as 'str' }; - export const int = { T: 0 as number, kind: 'int' as 'int' }; - export const float = { T: 0 as number, kind: 'float' as 'float' }; - + export type Str = { T: string, kind: 'str' } + export type Int = { T: number, kind: 'int' } + export type Float = { T: number, kind: 'float' } export type Vector = { T: number[], dim: number, kind: 'vector' }; export type Matrix = { T: number[][], rows: number, cols: number, kind: 'matrix' }; + export type Aliased<T> = { T: T } & { kind: 'str' | 'int' | 'float' } + + export const str: Str = { T: '', kind: 'str' }; + export const int: Int = { T: 0, kind: 'int' }; + export const float: Float = { T: 0, kind: 'float' }; + export function vector(dim: number): Vector { return { T: [] as number[], dim, kind: 'vector' }; } export function matrix(rows: number, cols: number): Matrix { return { T: [] as number[][], rows, cols, kind: 'matrix' }; } + export function aliased<T>(t: Type): Aliased<T> { return t as any as Aliased<T>; } } export interface ToArrayParams { diff --git a/src/mol-base/collections/table.ts b/src/mol-base/collections/table.ts index 152a894a28d0152fb0d73603c7c3ff6022c863ba..a7f7aed5af70b183ff39dd91460f0c2ab046b246 100644 --- a/src/mol-base/collections/table.ts +++ b/src/mol-base/collections/table.ts @@ -9,10 +9,21 @@ import { sortArray } from './sort' type Table<Schema extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & Table.Columns<Schema> +/** An immutable table */ namespace Table { export type Schema = { [field: string]: Column.Type } export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> } export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] } + export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> } + + export function pickColumns<S extends Schema, T extends S>(schema: S, table: Table<T>): Table<S> { + const ret = Object.create(null); + const keys = Object.keys(schema); + ret._rowCount = table._rowCount; + ret._columns = keys; + for (const k of keys) ret[k] = table[k]; + return ret; + } export function ofColumns<S extends Schema, R extends Table<S> = Table<S>>(columns: Columns<S>): R { const _columns = Object.keys(columns); @@ -26,7 +37,7 @@ namespace Table { const columns = Object.keys(schema); ret._rowCount = rowCount; ret._columns = columns; - for (const k of Object.keys(schema)) { + for (const k of columns) { (ret as any)[k] = Column.ofLambda({ rowCount, type: schema[k], @@ -37,6 +48,17 @@ namespace Table { return ret as R; } + export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R { + const ret = Object.create(null); + const columns = Object.keys(schema); + ret._rowCount = arrays[columns[0]].length; + ret._columns = columns; + for (const k of Object.keys(schema)) { + (ret as any)[k] = Column.ofArray({ array: arrays[k], type: schema[k] }) + } + return ret as R; + } + /** Sort and return a new table */ export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) { const indices = new Int32Array(table._rowCount); diff --git a/src/mol-data/model/properties/hierarchy.ts b/src/mol-data/model/properties/hierarchy.ts index 427b2a1bc5e6745467e094b54c2b959076f52b0c..5d54f59ba5995e11418fcb9add0a4721e72d31d1 100644 --- a/src/mol-data/model/properties/hierarchy.ts +++ b/src/mol-data/model/properties/hierarchy.ts @@ -5,7 +5,8 @@ */ import Column from '../../../mol-base/collections/column' -import { Shape as mmCIF } from '../../../mol-io/reader/cif/schema/mmcif' +import Table from '../../../mol-base/collections/table' +import { Schema as mmCIF } from '../../../mol-io/reader/cif/schema/mmcif' export interface ElementSymbol extends String { '@type': 'element-symbol' } export function ElementSymbol(s: string): ElementSymbol { @@ -13,50 +14,55 @@ export function ElementSymbol(s: string): ElementSymbol { return s.toUpperCase() as any; } -type Key = { key: Column<number> } - -type _Atoms = Pick<mmCIF['atom_site'], - | 'type_symbol' - | 'label_atom_id' - | 'auth_atom_id' - | 'label_alt_id' - | 'pdbx_formal_charge' - | 'occupancy' - | 'B_iso_or_equiv'> - & Key -export interface Atoms extends _Atoms { - source_row: Column<number> -} +export const AtomsSchema = { + type_symbol: Column.Type.aliased<ElementSymbol>(mmCIF.atom_site.type_symbol), + label_atom_id: mmCIF.atom_site.label_atom_id, + auth_atom_id: mmCIF.atom_site.auth_atom_id, + label_alt_id: mmCIF.atom_site.label_alt_id, + pdbx_formal_charge: mmCIF.atom_site.pdbx_formal_charge, + occupancy: mmCIF.atom_site.occupancy, + B_iso_or_equiv: mmCIF.atom_site.B_iso_or_equiv, + + key: Column.Type.int, + source_row: Column.Type.int, +}; + +export interface Atoms extends Table<typeof AtomsSchema> { } + +export const ResiduesSchema = { + group_PDB: mmCIF.atom_site.group_PDB, + label_comp_id: mmCIF.atom_site.label_comp_id, + auth_comp_id: mmCIF.atom_site.auth_comp_id, + label_seq_id: mmCIF.atom_site.label_seq_id, + auth_seq_id: mmCIF.atom_site.auth_seq_id, + pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code, -type _Residues = Pick<mmCIF['atom_site'], - | 'group_PDB' - | 'label_comp_id' - | 'auth_comp_id' - | 'label_seq_id' - | 'auth_seq_id' - | 'pdbx_PDB_ins_code'> - & Key -export interface Residues extends _Residues { } - -type _Chains = Pick<mmCIF['atom_site'], - | 'label_asym_id' - | 'auth_asym_id' - | 'auth_comp_id' - | 'label_entity_id' - | 'pdbx_PDB_model_num'> - & Key -export interface Chains extends _Chains { - enityDataIndex: Column<number> + key: Column.Type.int +}; + +export interface Residues extends Table<typeof AtomsSchema> { } + +export const ChainsSchema = { + label_asym_id: mmCIF.atom_site.label_asym_id, + auth_asym_id: mmCIF.atom_site.auth_asym_id, + auth_comp_id: mmCIF.atom_site.auth_comp_id, + label_entity_id: mmCIF.atom_site.label_entity_id, + pdbx_PDB_model_num: mmCIF.atom_site.pdbx_PDB_model_num, + + key: Column.Type.int, + entityIndex: Column.Type.int } -type _EntityData = mmCIF['entity'] -export interface EntityData extends _EntityData { } +export interface Chains extends Table<typeof ChainsSchema> { } + +export const EntitySchema = mmCIF['entity'] +export interface Entities extends Table<typeof EntitySchema> { } export interface Macromolecule { atoms: Atoms, residues: Residues, chains: Chains, - entityData: EntityData + entities: Entities } export default Macromolecule \ No newline at end of file diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts index accb0ab6abf1d8df80344aed155d013653f9435a..553c01917afae6eb1343754471d78ecefb158f9d 100644 --- a/src/mol-io/reader/_spec/cif.spec.ts +++ b/src/mol-io/reader/_spec/cif.spec.ts @@ -21,7 +21,7 @@ const testBlock = Data.Block({ }, 'test'); namespace TestSchema { - export const atoms = { x: Schema.Field.int(), name: Schema.Field.str() } + export const atoms = { x: Schema.Types.int, name: Schema.Types.str } export const schema = { atoms } } diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index adf421e3bb3df8159ed5ddbfc212841d5a744551..113ed90849f66613882cfa45e440054c3dd66243 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/src/mol-io/reader/cif/schema.ts @@ -6,93 +6,56 @@ import * as Data from './data-model' import Column, { createAndFillArray } from '../../../mol-base/collections/column' - -/** - * A schema defines the shape of categories and fields. - * - * @example: - * const atom_site = { - * '@alias': '_atom_site', - * label_atom_id: Field.str(), - * Cartn_x: Field.float(), - * Cartn_y: Field.float(), - * Cartn_z: Field.float(), - * } - * - * const mmCIF = { atom_site }; - */ - -////////////////////////////////////////////// +import Table from '../../../mol-base/collections/table' export function toTypedFrame<Schema extends FrameSchema, Frame extends TypedFrame<Schema> = TypedFrame<Schema>>(schema: Schema, frame: Data.Frame): Frame { return createTypedFrame(schema, frame) as Frame; } -export function toTypedCategory<Schema extends CategorySchema>(schema: Schema, category: Data.Category): TypedCategory<Schema> { - return new _TypedCategory(category, schema, true) as TypedCategory<any>; +export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.Category): R { + return new _TypedCategory(category, schema, true) as any; } -export type FrameSchema = { [category: string]: CategorySchema } -export type TypedFrameShape<Schema extends FrameSchema> = { [C in keyof Schema]: TypedCategoryShape<Schema[C]> } +export const Types = Column.Type + +export type FrameSchema = { [category: string]: Table.Schema } export type TypedFrame<Schema extends FrameSchema> = { readonly _header?: string, readonly _frame: Data.Frame -} & { [C in keyof Schema]: TypedCategory<Schema[C]> } - -export type CategorySchema = { [field: string]: Field.Schema<any> } -export type TypedCategoryShape<Schema extends CategorySchema> = { [F in keyof Schema]: Column<Schema[F]['T']> } -export type TypedCategory<Schema extends CategorySchema> = { - readonly _rowCount: number, - readonly _isDefined: boolean, - readonly _category: Data.Category -} & { [F in keyof Schema]: Column<Schema[F]['T']> } - -export namespace Field { - export interface Schema<T> { T: T, ctor: (field: Data.Field, category: Data.Category, key: string) => Column<T>, undefinedField: (c: number) => Data.Field, alias?: string }; - export interface Spec { undefinedField?: (c: number) => Data.Field, alias?: string } - - export function alias(name: string): Schema<any> { return { alias: name } as any; } - export function str(spec?: Spec) { return createSchema(spec, Str); } - export function int(spec?: Spec) { return createSchema(spec, Int); } - export function float(spec?: Spec) { return createSchema(spec, Float); } - export function vector(rows: number, spec?: Spec) { return createSchema(spec, Vector(rows)); } - export function matrix(rows: number, cols: number, spec?: Spec) { return createSchema(spec, Matrix(rows, cols)); } - - function create<T>(type: Column.Type, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> { - return { - '@type': type, - '@array': field['@array'], - isDefined: field.isDefined, - rowCount: field.rowCount, - value, - valueKind: field.valueKind, - areValuesEqual: field.areValuesEqual, - toArray - }; - } - - function Str(field: Data.Field) { return create(Column.Type.str, field, field.str, field.toStringArray); } - function Int(field: Data.Field) { return create(Column.Type.int, field, field.int, field.toIntArray); } - function Float(field: Data.Field) { return create(Column.Type.float, field, field.float, field.toFloatArray); } - - function Vector(rows: number) { - return function(field: Data.Field, category: Data.Category, key: string) { - const value = (row: number) => Data.getVector(category, key, rows, row); - return create(Column.Type.vector(rows), field, value, params => createAndFillArray(field.rowCount, value, params)); +} & { [C in keyof Schema]: Table<Schema[C]> } + +type ColumnCtor = (field: Data.Field, category: Data.Category, key: string) => Column<any> + +function getColumnCtor(t: Column.Type): ColumnCtor { + switch (t.kind) { + case 'str': return (f, c, k) => createColumn(Column.Type.str, f, f.str, f.toStringArray); + case 'int': return (f, c, k) => createColumn(Column.Type.int, f, f.int, f.toIntArray); + case 'float': return (f, c, k) => createColumn(Column.Type.float, f, f.float, f.toFloatArray); + case 'vector': return (f, c, k) => { + const dim = t.dim; + const value = (row: number) => Data.getVector(c, k, dim, row); + return createColumn(t, f, value, params => createAndFillArray(f.rowCount, value, params)); } - } - - function Matrix(rows: number, cols: number) { - return function(field: Data.Field, category: Data.Category, key: string) { - const value = (row: number) => Data.getMatrix(category, key, rows, cols, row); - return create(Column.Type.matrix(rows, cols), field, value, params => createAndFillArray(field.rowCount, value, params)); + case 'matrix': return (f, c, k) => { + const rows = t.rows, cols = t.cols; + const value = (row: number) => Data.getMatrix(c, k, rows, cols, row); + return createColumn(t, f, value, params => createAndFillArray(f.rowCount, value, params)); } } +} - // spec argument is to allow for specialised implementation for undefined fields - function createSchema<T>(spec: Spec | undefined, ctor: (field: Data.Field, category: Data.Category, key: string) => Column<T>): Schema<T> { - return { T: 0 as any, ctor, undefinedField: (spec && spec.undefinedField) || Data.DefaultUndefinedField, alias: spec && spec.alias }; - } + +function createColumn<T>(type: Column.Type, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> { + return { + '@type': type, + '@array': field['@array'], + isDefined: field.isDefined, + rowCount: field.rowCount, + value, + valueKind: field.valueKind, + areValuesEqual: field.areValuesEqual, + toArray + }; } class _TypedFrame implements TypedFrame<any> { // tslint:disable-line:class-name @@ -104,19 +67,21 @@ class _TypedFrame implements TypedFrame<any> { // tslint:disable-line:class-name } } -class _TypedCategory implements TypedCategory<any> { // tslint:disable-line:class-name +class _TypedCategory implements Table<any> { // tslint:disable-line:class-name _rowCount = this._category.rowCount; - constructor(public _category: Data.Category, schema: CategorySchema, public _isDefined: boolean) { - const fieldKeys = Object.keys(schema).filter(k => k !== '@alias'); + _columns: ReadonlyArray<string>; + constructor(public _category: Data.Category, schema: Table.Schema, public _isDefined: boolean) { + const fieldKeys = Object.keys(schema); + this._columns = fieldKeys; const cache = Object.create(null); for (const k of fieldKeys) { - const s = schema[k]; + const cType = schema[k]; + const ctor = getColumnCtor(cType); Object.defineProperty(this, k, { get: function() { if (cache[k]) return cache[k]; - const name = s.alias || k; - const field = _category.getField(name) || s.undefinedField(_category.rowCount); - cache[k] = s.ctor(field, _category, name); + const field = _category.getField(k); + cache[k] = !!field ? ctor(field, _category, k) : Column.Undefined(_category.rowCount, cType); return cache[k]; }, enumerable: true, @@ -130,9 +95,7 @@ function createTypedFrame(schema: FrameSchema, frame: Data.Frame): any { return new _TypedFrame(frame, schema); } -function createTypedCategory(key: string, schema: CategorySchema, frame: Data.Frame) { - const alias = (schema['@alias'] && schema['@alias'].alias) || key; - const name = alias[0] === '_' ? alias : '_' + alias; - const cat = frame.categories[name]; +function createTypedCategory(key: string, schema: Table.Schema, frame: Data.Frame) { + const cat = frame.categories[key[0] === '_' ? key : '_' + key]; return new _TypedCategory(cat || Data.Category.Empty, schema, !!cat); } \ No newline at end of file diff --git a/src/mol-io/reader/cif/schema/dic.ts b/src/mol-io/reader/cif/schema/dic.ts index 77e4dac7e9c74c0e26ce0cbe242dae88a538c11f..4d20eccc89ad032c556fdbe8df256109a330cb15 100644 --- a/src/mol-io/reader/cif/schema/dic.ts +++ b/src/mol-io/reader/cif/schema/dic.ts @@ -4,10 +4,10 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Field, TypedFrame } from '../schema' +import { Types, TypedFrame } from '../schema' -const str = Field.str() -const float = Field.float() +const str = Types.str +const float = Types.float const datablock = { id: str, @@ -58,7 +58,7 @@ const item_units_conversion = { // TODO save frame dic schema -const dic = { +export const Schema = { datablock, dictionary, dictionary_history, @@ -69,5 +69,7 @@ const dic = { item_units_conversion } -type dic = TypedFrame<typeof dic> -export default dic +export interface Frame extends TypedFrame<typeof Schema> { } + +// type dic = TypedFrame<typeof dic> +//export default dic diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index a5d3ba1567e63dc17b42123231f1e3195c2a3141..839ee23e8c7b5e39cf5294b71634c96e5e6dbd98 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/src/mol-io/reader/cif/schema/mmcif.ts @@ -4,11 +4,11 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Field, TypedFrame, TypedFrameShape } from '../schema' +import { Types, TypedFrame } from '../schema' -const str = Field.str(); -const int = Field.int(); -const float = Field.float(); +const str = Types.str; +const int = Types.int; +const float = Types.float; const entry = { id: str @@ -18,7 +18,7 @@ type EntityType = 'polymer' | 'non-polymer' | 'water' const entity = { id: str, - type: str as Field.Schema<EntityType>, + type: Types.aliased<EntityType>(str), src_method: str, pdbx_description: str, formula_weight: float, @@ -48,8 +48,8 @@ const cell = { const symmetry = { entry_id: str, - space_group_name_HM: Field.str({ alias: 'space_group_name_H-M' }), - pdbx_full_space_group_name_HM: Field.str({ alias: 'pdbx_full_space_group_name_H-M' }), + 'space_group_name_H-M': str, + 'pdbx_full_space_group_name_H': str, cell_setting: str, Int_Tables_number: int, space_group_name_Hall: str @@ -117,7 +117,7 @@ type BondValueOrder = const struct_conn = { id: str, - conn_type_id: str as Field.Schema<StructConnTypeId>, + conn_type_id: Types.aliased<StructConnTypeId>(str), pdbx_PDB_id: str, ptnr1_label_asym_id: str, ptnr1_label_comp_id: str, @@ -148,11 +148,11 @@ const struct_conn = { pdbx_ptnr3_PDB_ins_code: str, details: str, pdbx_dist_value: float, - pdbx_value_order: str as Field.Schema<BondValueOrder> + pdbx_value_order: Types.aliased<BondValueOrder>(str) } const struct_conn_type = { - id: str as Field.Schema<StructConnTypeId>, + id: Types.aliased<StructConnTypeId>(str), criteria: str, reference: str } @@ -161,10 +161,10 @@ const chem_comp_bond = { comp_id: str, pdbx_stereo_config: str, pdbx_ordinal: int, - pdbx_aromatic_flag: str as Field.Schema<'Y' | 'N'>, + pdbx_aromatic_flag: Types.aliased<'Y' | 'N'>(str), atom_id_1: str, atom_id_2: str, - value_order: str as Field.Schema<BondValueOrder> + value_order: Types.aliased<BondValueOrder>(str) } const pdbx_struct_assembly = { @@ -186,8 +186,8 @@ const pdbx_struct_oper_list = { type: str, name: str, symmetry_operation: str, - matrix: Field.matrix(3, 3), - vector: Field.vector(3) + matrix: Types.matrix(3, 3), + vector: Types.vector(3) } const pdbx_struct_mod_residue = { @@ -245,5 +245,4 @@ export const Schema = { atom_site }; -export interface Frame extends TypedFrame<typeof Schema> { } -export interface Shape extends TypedFrameShape<typeof Schema> { } \ No newline at end of file +export interface Frame extends TypedFrame<typeof Schema> { } \ No newline at end of file