schema.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { DatabaseCollection, Database, Table, Column, ColumnHelpers } from 'mol-data/db'
  8. import { Tensor } from 'mol-math/linear-algebra'
  9. import { arrayEqual } from 'mol-util'
  10. import * as Data from './data-model'
  11. export function toDatabaseCollection<Schema extends Database.Schema>(schema: Schema, file: Data.File): DatabaseCollection<Schema> {
  12. const dbc: DatabaseCollection<Schema> = {}
  13. for (const data of file.blocks) {
  14. dbc[data.header] = toDatabase(schema, data)
  15. }
  16. return dbc;
  17. }
  18. export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.Frame): Frame {
  19. return createDatabase(schema, frame) as Frame;
  20. }
  21. export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.Category): R {
  22. return new CategoryTable(category, schema, true) as any;
  23. }
  24. type ColumnCtor = (field: Data.Field, category: Data.Category, key: string) => Column<any>
  25. function getColumnCtor(t: Column.Schema): ColumnCtor {
  26. switch (t.valueType) {
  27. case 'str': return (f, c, k) => createColumn(t, f, f.str, f.toStringArray);
  28. case 'int': return (f, c, k) => createColumn(t, f, f.int, f.toIntArray);
  29. case 'float': return (f, c, k) => createColumn(t, f, f.float, f.toFloatArray);
  30. case 'list': throw new Error('Use createListColumn instead.');
  31. case 'tensor': throw new Error('Use createTensorColumn instead.');
  32. }
  33. }
  34. function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> {
  35. return {
  36. schema,
  37. '@array': field['@array'],
  38. isDefined: field.isDefined,
  39. rowCount: field.rowCount,
  40. value,
  41. valueKind: field.valueKind,
  42. areValuesEqual: field.areValuesEqual,
  43. toArray
  44. };
  45. }
  46. function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.Category, key: string): Column<(number|string)[]> {
  47. const separator = schema.separator;
  48. const itemParse = schema.itemParse;
  49. const f = category.getField(key);
  50. const value = f ? (row: number) => f.str(row).split(separator).map(x => itemParse(x.trim())).filter(x => !!x) : (row: number) => []
  51. const toArray: Column<T[]>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params)
  52. return {
  53. schema,
  54. '@array': void 0,
  55. isDefined: !!f,
  56. rowCount: category.rowCount,
  57. value,
  58. valueKind: f ? f.valueKind : () => Column.ValueKind.NotPresent,
  59. areValuesEqual: (rowA, rowB) => arrayEqual(value(rowA), value(rowB)),
  60. toArray
  61. };
  62. }
  63. function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor> {
  64. const space = schema.space;
  65. let firstFieldName: string;
  66. switch (space.rank) {
  67. case 1: firstFieldName = `${key}[1]`; break;
  68. case 2: firstFieldName = `${key}[1][1]`; break;
  69. case 3: firstFieldName = `${key}[1][1][1]`; break;
  70. default: throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.');
  71. }
  72. const first = category.getField(firstFieldName) || Column.Undefined(category.rowCount, schema);
  73. const value = (row: number) => Data.getTensor(category, key, space, row);
  74. const toArray: Column<Tensor>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params)
  75. return {
  76. schema,
  77. '@array': void 0,
  78. isDefined: first.isDefined,
  79. rowCount: category.rowCount,
  80. value,
  81. valueKind: first.valueKind,
  82. areValuesEqual: (rowA, rowB) => Tensor.areEqualExact(value(rowA), value(rowB)),
  83. toArray
  84. };
  85. }
  86. class CategoryTable implements Table<any> { // tslint:disable-line:class-name
  87. _rowCount: number;
  88. _columns: ReadonlyArray<string>;
  89. _schema: any;
  90. [k: string]: any;
  91. constructor(category: Data.Category, schema: Table.Schema, public _isDefined: boolean) {
  92. const fieldKeys = Object.keys(schema);
  93. this._rowCount = category.rowCount;
  94. this._columns = fieldKeys;
  95. this._schema = schema;
  96. const cache = Object.create(null);
  97. for (const k of fieldKeys) {
  98. Object.defineProperty(this, k, {
  99. get: function() {
  100. if (cache[k]) return cache[k];
  101. const fType = schema[k];
  102. if (fType.valueType === 'list') {
  103. cache[k] = createListColumn(fType, category, k);
  104. } else if (fType.valueType === 'tensor') {
  105. cache[k] = createTensorColumn(fType, category, k);
  106. } else {
  107. const ctor = getColumnCtor(fType);
  108. const field = category.getField(k);
  109. cache[k] = !!field ? ctor(field, category, k) : Column.Undefined(category.rowCount, fType);
  110. }
  111. return cache[k];
  112. },
  113. enumerable: true,
  114. configurable: false
  115. });
  116. }
  117. }
  118. }
  119. function createDatabase(schema: Database.Schema, frame: Data.Frame): Database<any> {
  120. const tables = Object.create(null);
  121. for (const k of Object.keys(schema)) {
  122. tables[k] = createTable(k, (schema as any)[k], frame);
  123. }
  124. return Database.ofTables(frame.header, schema, tables);
  125. }
  126. function createTable(key: string, schema: Table.Schema, frame: Data.Frame) {
  127. const cat = frame.categories[key];
  128. return new CategoryTable(cat || Data.Category.empty(key), schema, !!cat);
  129. }