From ba2a4f3a99ae98b4b9dae3ee9f476f290f67d056 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Thu, 10 May 2018 16:10:54 +0200 Subject: [PATCH] More refactoring of mol-model --- package-lock.json | Bin 478290 -> 489768 bytes package.json | 2 +- src/apps/structure-info/model.ts | 12 +- .../representation/structure/spacefill.ts | 2 +- src/mol-geo/theme/structure/color/chain-id.ts | 10 +- .../theme/structure/color/element-symbol.ts | 2 +- src/mol-geo/theme/structure/size/element.ts | 2 +- src/mol-math/linear-algebra/3d/mat3.ts | 4 + src/mol-math/linear-algebra/tensor.ts | 12 +- src/mol-model/structure/export/mmcif.ts | 2 +- src/mol-model/structure/model/format.ts | 6 +- src/mol-model/structure/model/formats/gro.ts | 307 +++++++++--------- .../structure/model/formats/mmcif.ts | 52 +-- .../structure/model/formats/mmcif/assembly.ts | 2 +- .../structure/model/formats/mmcif/bonds.ts | 2 +- .../structure/model/formats/mmcif/ihm.ts | 87 +++-- .../structure/model/formats/mmcif/util.ts | 4 +- src/mol-model/structure/model/model.ts | 26 +- .../model/properties/atomic/hierarchy.ts | 17 +- .../properties/coarse-grained/conformation.ts | 0 .../properties/coarse-grained/hierarchy.ts | 53 --- .../structure/model/properties/coarse.ts | 8 + .../model/properties/coarse/conformation.ts | 30 ++ .../model/properties/coarse/hierarchy.ts | 49 +++ .../structure/model/properties/common.ts | 16 - .../utils/atomic-keys.ts} | 17 +- .../model/properties/utils/coarse-keys.ts | 84 +++++ .../{ => properties}/utils/guess-element.ts | 0 src/mol-model/structure/query/generators.ts | 8 +- src/mol-model/structure/query/properties.ts | 66 ++-- .../structure/structure/structure.ts | 19 +- src/mol-model/structure/structure/unit.ts | 54 +-- .../structure/unit/bonds/intra-compute.ts | 6 +- src/perf-tests/structure.ts | 4 +- 34 files changed, 560 insertions(+), 405 deletions(-) delete mode 100644 src/mol-model/structure/model/properties/coarse-grained/conformation.ts delete mode 100644 src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts create mode 100644 src/mol-model/structure/model/properties/coarse.ts create mode 100644 src/mol-model/structure/model/properties/coarse/conformation.ts create mode 100644 src/mol-model/structure/model/properties/coarse/hierarchy.ts rename src/mol-model/structure/model/{utils/hierarchy-keys.ts => properties/utils/atomic-keys.ts} (90%) create mode 100644 src/mol-model/structure/model/properties/utils/coarse-keys.ts rename src/mol-model/structure/model/{ => properties}/utils/guess-element.ts (100%) diff --git a/package-lock.json b/package-lock.json index 2d93002f4e7b77be351e8e4eb333e62b3f5807b4..c1e108171140e7de69fdc590f15d660d717faef1 100644 GIT binary patch delta 20444 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	*zdWjsf$8-=tFv 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 K{=CNX8vh?(mT0a3 delta 20628 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 diff --git a/package.json b/package.json index fc7b94070..23e18880b 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "argparse": "^1.0.10", "express": "^4.16.3", "gl": "^4.0.4", - "material-ui": "^1.0.0-beta.46", + "material-ui": "~1.0.0-beta.43", "node-fetch": "^2.1.2", "react": "^16.3.2", "react-dom": "^16.3.2", diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 1bc3b500b..15ee55fd8 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -30,7 +30,7 @@ async function readPdbFile(path: string) { } export function atomLabel(model: Model, aI: number) { - const { atoms, residues, chains, residueSegments, chainSegments } = model.hierarchy + const { atoms, residues, chains, residueSegments, chainSegments } = model.atomicHierarchy const { label_atom_id } = atoms const { label_comp_id, label_seq_id } = residues const { label_asym_id } = chains @@ -92,10 +92,10 @@ export function printUnits(structure: Structure) { } else if (Unit.isCoarse(l.unit)) { console.log(`Coarse unit ${unit.id} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`); - const props = Queries.props.coarse_grained; + const props = Queries.props.coarse; const seq = l.unit.model.sequence; - for (let j = 0, _j = Math.min(size, 10); j < _j; j++) { + for (let j = 0, _j = Math.min(size, 3); j < _j; j++) { l.element = OrderedSet.getAt(elements, j); const residues: string[] = []; @@ -104,16 +104,16 @@ export function printUnits(structure: Structure) { for (let e = start; e <= end; e++) residues.push(compId(e)); console.log(`${props.asym_id(l)}:${start}-${end} (${residues.join('-')}) ${props.asym_id(l)} [${props.x(l).toFixed(2)}, ${props.y(l).toFixed(2)}, ${props.z(l).toFixed(2)}]`); } - if (size > 10) console.log(`...`); + if (size > 3) console.log(`...`); } } } export function printIHMModels(model: Model) { - if (!model.coarseGrained.isDefined) return false; + if (!model.coarseHierarchy.isDefined) return false; console.log('IHM Models\n============='); - console.log(Table.formatToString(model.coarseGrained.modelList)); + console.log(Table.formatToString(model.coarseHierarchy.models)); } async function run(mmcif: mmCIF_Database) { diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 1946f8103..db8240514 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -39,7 +39,7 @@ function createSpacefillMesh(unit: Unit, detail: number) { if (Unit.isAtomic(unit)) { radius = Queries.props.atom.vdw_radius } else if (Unit.isSpheres(unit)) { - radius = Queries.props.coarse_grained.sphere_radius + radius = Queries.props.coarse.sphere_radius } else { console.warn('Unsupported unit type') return meshBuilder.getMesh() diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts index 95e305be3..d521d6036 100644 --- a/src/mol-geo/theme/structure/color/chain-id.ts +++ b/src/mol-geo/theme/structure/color/chain-id.ts @@ -18,11 +18,11 @@ function createChainIdMap(unit: Unit) { let count: number let asym_id: Column<string> if (Unit.isAtomic(unit)) { - asym_id = unit.model.hierarchy.chains.label_asym_id - count = unit.model.hierarchy.chains._rowCount + asym_id = unit.model.atomicHierarchy.chains.label_asym_id + count = unit.model.atomicHierarchy.chains._rowCount } else if (Unit.isCoarse(unit)) { - asym_id = unit.sites.asym_id - count = unit.sites.count + asym_id = unit.coarseElements.asym_id + count = unit.coarseElements.count } else { console.warn('Unknown unit type') return { map, count: index } @@ -51,7 +51,7 @@ export function chainIdColorData(props: StructureColorDataProps) { if (Unit.isAtomic(unit)) { asym_id = Queries.props.chain.label_asym_id } else if (Unit.isCoarse(unit)) { - asym_id = Queries.props.coarse_grained.asym_id + asym_id = Queries.props.coarse.asym_id } const l = Element.Location() diff --git a/src/mol-geo/theme/structure/color/element-symbol.ts b/src/mol-geo/theme/structure/color/element-symbol.ts index 815985b33..affedfac0 100644 --- a/src/mol-geo/theme/structure/color/element-symbol.ts +++ b/src/mol-geo/theme/structure/color/element-symbol.ts @@ -23,7 +23,7 @@ export function elementSymbolColor(element: ElementSymbol): Color { export function elementSymbolColorData(props: StructureColorDataProps) { const { group: { units, elements }, vertexMap } = props - const { type_symbol } = units[0].model.hierarchy.atoms + const { type_symbol } = units[0].model.atomicHierarchy.atoms return createAttributeOrElementColor(vertexMap, { colorFn: (elementIdx: number) => { const e = elements[elementIdx] diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/element.ts index 8bf99acc0..741ba136f 100644 --- a/src/mol-geo/theme/structure/size/element.ts +++ b/src/mol-geo/theme/structure/size/element.ts @@ -17,7 +17,7 @@ export function elementSizeData(props: StructureSizeDataProps) { if (Unit.isAtomic(unit)) { radius = Queries.props.atom.vdw_radius } else if (Unit.isSpheres(unit)) { - radius = Queries.props.coarse_grained.sphere_radius + radius = Queries.props.coarse.sphere_radius } const l = Element.Location() l.unit = unit diff --git a/src/mol-math/linear-algebra/3d/mat3.ts b/src/mol-math/linear-algebra/3d/mat3.ts index cd577776b..c72dc713d 100644 --- a/src/mol-math/linear-algebra/3d/mat3.ts +++ b/src/mol-math/linear-algebra/3d/mat3.ts @@ -104,6 +104,10 @@ namespace Mat3 { return Mat3.copy(Mat3.zero(), a); } + export function setValue(a: Mat3, i: number, j: number, value: number) { + a[3 * j + i] = value; + } + /** * Copy the values from one Mat3 to another */ diff --git a/src/mol-math/linear-algebra/tensor.ts b/src/mol-math/linear-algebra/tensor.ts index 976e410e1..4bcae46f7 100644 --- a/src/mol-math/linear-algebra/tensor.ts +++ b/src/mol-math/linear-algebra/tensor.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Mat4, Vec3, Vec4 } from './3d' +import { Mat4, Vec3, Vec4, Mat3 } from './3d' export interface Tensor { data: Tensor.Data, space: Tensor.Space } @@ -65,6 +65,16 @@ export namespace Tensor { return mat; } + export function toMat3(space: Space, data: Tensor.Data): Mat3 { + if (space.rank !== 2) throw new Error('Invalid tensor rank'); + const mat = Mat3.zero(); + const d0 = Math.min(3, space.dimensions[0]), d1 = Math.min(3, space.dimensions[1]); + for (let i = 0; i < d0; i++) { + for (let j = 0; j < d1; j++) Mat3.setValue(mat, i, j, space.get(data, i, j)); + } + return mat; + } + export function toVec3(space: Space, data: Tensor.Data): Vec3 { if (space.rank !== 1) throw new Error('Invalid tensor rank'); const vec = Vec3.zero(); diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index 65ded0cbb..d7f4491a6 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -96,7 +96,7 @@ const atom_site: Encoder.CategoryDefinition<Element.Location> = { str('auth_asym_id', P.chain.auth_asym_id), int('pdbx_PDB_model_num', P.unit.model_num), - str('pdbx_operator_name', P.unit.operator_name) + str('operator_name', P.unit.operator_name) ] }; diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts index d463c30d7..2fa1f8ae1 100644 --- a/src/mol-model/structure/model/format.ts +++ b/src/mol-model/structure/model/format.ts @@ -4,15 +4,15 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { File as GroFile } from 'mol-io/reader/gro/schema' +// import { File as GroFile } from 'mol-io/reader/gro/schema' import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif' type Format = - | Format.gro + // | Format.gro | Format.mmCIF namespace Format { - export interface gro { kind: 'gro', data: GroFile } + // export interface gro { kind: 'gro', data: GroFile } export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database } } diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts index 46a9cd165..5e5350974 100644 --- a/src/mol-model/structure/model/formats/gro.ts +++ b/src/mol-model/structure/model/formats/gro.ts @@ -1,153 +1,154 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import UUID from 'mol-util/uuid' -import { Column, Table } from 'mol-data/db' -import { Interval, Segmentation } from 'mol-data/int' -import { Atoms } from 'mol-io/reader/gro/schema' -import Format from '../format' -import Model from '../model' -import { AtomicConformation, AtomicData, AtomsSchema, ResiduesSchema, ChainsSchema, AtomicSegments } from '../properties/atomic' -import { CoarseGrainedHierarchy } from '../properties/coarse-grained/hierarchy' -import findHierarchyKeys from '../utils/hierarchy-keys' -import { guessElement } from '../utils/guess-element' -import { ElementSymbol} from '../types' -import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif' - -import gro_Format = Format.gro -import Sequence from '../properties/sequence'; -import { Entities } from '../properties/common'; -import { ModelSymmetry } from '../properties/symmetry'; - -type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> } - -function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) { - const start = Interval.start(bounds), end = Interval.end(bounds); - const residues = [start], chains = [start]; - - const { residueName, residueNumber } = atomsData; - - for (let i = start + 1; i < end; i++) { - const newResidue = !residueNumber.areValuesEqual(i - 1, i) - || !residueName.areValuesEqual(i - 1, i); - console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue) - if (newResidue) residues[residues.length] = i; - } - console.log(residues, residues.length) - return { residues, chains }; -} - -function guessElementSymbol (value: string) { - return ElementSymbol(guessElement(value)); -} - -function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicData { - console.log(atomsData.atomName) - const atoms = Table.ofColumns(AtomsSchema, { - type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }), - label_atom_id: atomsData.atomName, - auth_atom_id: atomsData.atomName, - label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str), - pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int) - }); - - const residues = Table.view(Table.ofColumns(ResiduesSchema, { - group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)), - label_comp_id: atomsData.residueName, - auth_comp_id: atomsData.residueName, - label_seq_id: atomsData.residueNumber, - auth_seq_id: atomsData.residueNumber, - pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str), - }), ResiduesSchema, offsets.residues); - // Optimize the numeric columns - Table.columnToArray(residues, 'label_seq_id', Int32Array); - Table.columnToArray(residues, 'auth_seq_id', Int32Array); - - // const chains = Table.ofColumns(Hierarchy.ChainsSchema, { - // label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str), - // auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str), - // label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str) - // }); - - const chains = Table.ofUndefinedColumns(ChainsSchema, 0); - - return { atoms, residues, chains }; -} - -function getConformation(atoms: Atoms): AtomicConformation { - return { - id: UUID.create(), - atomId: atoms.atomNumber, - occupancy: Column.Undefined(atoms.count, Column.Schema.int), - B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float), - x: Column.mapToArray(atoms.x, x => x * 10, Float32Array), - y: Column.mapToArray(atoms.y, y => y * 10, Float32Array), - z: Column.mapToArray(atoms.z, z => z * 10, Float32Array) - } -} - -function isHierarchyDataEqual(a: AtomicData, b: AtomicData) { - // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300 - return Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>) - && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>) -} - -function createModel(format: gro_Format, modelNum: number, previous?: Model): Model { - const structure = format.data.structures[modelNum]; - const bounds = Interval.ofBounds(0, structure.atoms.count); - - const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds); - const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets); - - if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) { - return { - ...previous, - atomSiteConformation: getConformation(structure.atoms) - }; - } - - const hierarchySegments: AtomicSegments = { - residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds), - chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds), - } - - // TODO: create a better mock entity - const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{ - id: '0', - src_method: 'syn', - type: 'polymer', - pdbx_number_of_molecules: 1 - }]); - - const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) }; - - const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments); - const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments }; - return { - id: UUID.create(), - sourceData: format, - modelNum, - hierarchy, - entities, - sequence: Sequence.fromAtomicHierarchy(hierarchy), - atomSiteConformation: getConformation(structure.atoms), - coarseGrained: CoarseGrainedHierarchy.Empty, - symmetry: ModelSymmetry.Default, - atomCount: structure.atoms.count - }; -} - -function buildModels(format: gro_Format): ReadonlyArray<Model> { - const models: Model[] = []; - - format.data.structures.forEach((_, i) => { - const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0); - models.push(model); - }); - return models; -} - -export default buildModels; \ No newline at end of file +// TODO: make this work when the time comes. +// /** +// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author Alexander Rose <alexander.rose@weirdbyte.de> +// */ + +// import { Column, Table } from 'mol-data/db'; +// import { Interval, Segmentation } from 'mol-data/int'; +// import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'; +// import { Atoms } from 'mol-io/reader/gro/schema'; +// import UUID from 'mol-util/uuid'; +// import Format from '../format'; +// import Model from '../model'; +// import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic'; +// import { CoarseHierarchy } from '../properties/coarse'; +// import { Entities } from '../properties/common'; +// import Sequence from '../properties/sequence'; +// import { ModelSymmetry } from '../properties/symmetry'; +// import { guessElement } from '../properties/utils/guess-element'; +// import { getAtomicKeys } from '../properties/utils/keys'; +// import { ElementSymbol } from '../types'; + +// import gro_Format = Format.gro + +// type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> } + +// function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) { +// const start = Interval.start(bounds), end = Interval.end(bounds); +// const residues = [start], chains = [start]; + +// const { residueName, residueNumber } = atomsData; + +// for (let i = start + 1; i < end; i++) { +// const newResidue = !residueNumber.areValuesEqual(i - 1, i) +// || !residueName.areValuesEqual(i - 1, i); +// console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue) +// if (newResidue) residues[residues.length] = i; +// } +// console.log(residues, residues.length) +// return { residues, chains }; +// } + +// function guessElementSymbol (value: string) { +// return ElementSymbol(guessElement(value)); +// } + +// function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicData { +// console.log(atomsData.atomName) +// const atoms = Table.ofColumns(AtomsSchema, { +// type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }), +// label_atom_id: atomsData.atomName, +// auth_atom_id: atomsData.atomName, +// label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str), +// pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int) +// }); + +// const residues = Table.view(Table.ofColumns(ResiduesSchema, { +// group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)), +// label_comp_id: atomsData.residueName, +// auth_comp_id: atomsData.residueName, +// label_seq_id: atomsData.residueNumber, +// auth_seq_id: atomsData.residueNumber, +// pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str), +// }), ResiduesSchema, offsets.residues); +// // Optimize the numeric columns +// Table.columnToArray(residues, 'label_seq_id', Int32Array); +// Table.columnToArray(residues, 'auth_seq_id', Int32Array); + +// // const chains = Table.ofColumns(Hierarchy.ChainsSchema, { +// // label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str), +// // auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str), +// // label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str) +// // }); + +// const chains = Table.ofUndefinedColumns(ChainsSchema, 0); + +// return { atoms, residues, chains }; +// } + +// function getConformation(atoms: Atoms): AtomicConformation { +// return { +// id: UUID.create(), +// atomId: atoms.atomNumber, +// occupancy: Column.Undefined(atoms.count, Column.Schema.int), +// B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float), +// x: Column.mapToArray(atoms.x, x => x * 10, Float32Array), +// y: Column.mapToArray(atoms.y, y => y * 10, Float32Array), +// z: Column.mapToArray(atoms.z, z => z * 10, Float32Array) +// } +// } + +// function isHierarchyDataEqual(a: AtomicData, b: AtomicData) { +// // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300 +// return Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>) +// && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>) +// } + +// function createModel(format: gro_Format, modelNum: number, previous?: Model): Model { +// const structure = format.data.structures[modelNum]; +// const bounds = Interval.ofBounds(0, structure.atoms.count); + +// const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds); +// const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets); + +// if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) { +// return { +// ...previous, +// atomicConformation: getConformation(structure.atoms) +// }; +// } + +// const hierarchySegments: AtomicSegments = { +// residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds), +// chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds), +// } + +// // TODO: create a better mock entity +// const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{ +// id: '0', +// src_method: 'syn', +// type: 'polymer', +// pdbx_number_of_molecules: 1 +// }]); + +// const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) }; + +// const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments); +// const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments }; +// return { +// id: UUID.create(), +// sourceData: format, +// modelNum, +// atomicHierarchy, +// entities, +// sequence: Sequence.fromAtomicHierarchy(atomicHierarchy), +// atomicConformation: getConformation(structure.atoms), +// coarseHierarchy: CoarseHierarchy.Empty, +// coarseConformation: void 0 as any, +// symmetry: ModelSymmetry.Default +// }; +// } + +// function buildModels(format: gro_Format): ReadonlyArray<Model> { +// const models: Model[] = []; + +// format.data.structures.forEach((_, i) => { +// const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0); +// models.push(model); +// }); +// return models; +// } + +// export default buildModels; \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 22644e2fa..3f68d5444 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -4,23 +4,23 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import UUID from 'mol-util/uuid' -import { Column, Table } from 'mol-data/db' -import { Interval, Segmentation } from 'mol-data/int' -import Format from '../format' -import Model from '../model' -import { AtomsSchema, ResiduesSchema, ChainsSchema, AtomicData, AtomicSegments, AtomicConformation } from '../properties/atomic' -import { ModelSymmetry } from '../properties/symmetry' -import findHierarchyKeys from '../utils/hierarchy-keys' -import { ElementSymbol} from '../types' -import createAssemblies from './mmcif/assembly' - -import mmCIF_Format = Format.mmCIF -import { getSequence } from './mmcif/sequence'; -import { Entities } from '../properties/common'; -import { coarseGrainedFromIHM } from './mmcif/ihm'; +import { Column, Table } from 'mol-data/db'; +import { Interval, Segmentation } from 'mol-data/int'; import { Spacegroup, SpacegroupCell } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; +import UUID from 'mol-util/uuid'; +import Format from '../format'; +import Model from '../model'; +import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic'; +import { Entities } from '../properties/common'; +import { ModelSymmetry } from '../properties/symmetry'; +import { getAtomicKeys } from '../properties/utils/atomic-keys'; +import { ElementSymbol } from '../types'; +import { createAssemblies } from './mmcif/assembly'; +import { getIHMCoarse } from './mmcif/ihm'; +import { getSequence } from './mmcif/sequence'; + +import mmCIF_Format = Format.mmCIF function findModelBounds({ data }: mmCIF_Format, startIndex: number) { const num = data.atom_site.pdbx_PDB_model_num; @@ -120,10 +120,10 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): const hierarchyOffsets = findHierarchyOffsets(format, bounds); const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets); - if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) { + if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) { return { ...previous, - atomSiteConformation: getConformation(format, bounds) + atomicConformation: getConformation(format, bounds) }; } @@ -134,21 +134,23 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): const entities: Entities = { data: format.data.entity, getEntityIndex: Column.createIndexer(format.data.entity.id) }; - const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments); + const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments); + + const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments }; - const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments }; + const coarse = getIHMCoarse(format.data, entities); return { id: UUID.create(), sourceData: format, modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)), entities, - hierarchy, - sequence: getSequence(format.data, entities, hierarchy), - atomSiteConformation: getConformation(format, bounds), - coarseGrained: coarseGrainedFromIHM(format.data, entities), - symmetry: getSymmetry(format), - atomCount: Interval.size(bounds) + atomicHierarchy, + sequence: getSequence(format.data, entities, atomicHierarchy), + atomicConformation: getConformation(format, bounds), + coarseHierarchy: coarse.hierarchy, + coarseConformation: coarse.conformation, + symmetry: getSymmetry(format) }; } diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts index 2315133d4..48720596c 100644 --- a/src/mol-model/structure/model/formats/mmcif/assembly.ts +++ b/src/mol-model/structure/model/formats/mmcif/assembly.ts @@ -12,7 +12,7 @@ import { Queries as Q, Query } from '../../../query' import mmCIF_Format = Format.mmCIF -export default function create(format: mmCIF_Format): ReadonlyArray<Assembly> { +export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> { const { pdbx_struct_assembly } = format.data; if (!pdbx_struct_assembly._rowCount) return []; diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts index 7bd6ec6f9..95ac32d4d 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds.ts +++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts @@ -118,7 +118,7 @@ export namespace StructConn { const _p = (row: number, ps: typeof p1) => { if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0; const asymId = ps.label_asym_id.value(row) - const residueIndex = model.hierarchy.findResidueKey( + const residueIndex = model.atomicHierarchy.findResidueKey( findEntityIdByAsymId(model, asymId), ps.label_comp_id.value(row), asymId, diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts index 5eab89ab0..1dfdc1c1d 100644 --- a/src/mol-model/structure/model/formats/mmcif/ihm.ts +++ b/src/mol-model/structure/model/formats/mmcif/ihm.ts @@ -5,50 +5,81 @@ */ import { mmCIF_Database as mmCIF, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif' -import { CoarseGrainedHierarchy } from '../../properties/coarse-grained/hierarchy' +import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../properties/coarse' import { Entities } from '../../properties/common'; import { Column } from 'mol-data/db'; +import { getCoarseKeys } from '../../properties/utils/coarse-keys'; +import { UUID } from 'mol-util'; +import { Segmentation, Interval } from 'mol-data/int'; +import { Mat3, Tensor } from 'mol-math/linear-algebra'; -function coarseGrainedFromIHM(data: mmCIF, entities: Entities): CoarseGrainedHierarchy { - if (data.ihm_model_list._rowCount === 0) return CoarseGrainedHierarchy.Empty; +export function getIHMCoarse(data: mmCIF, entities: Entities): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } { + if (data.ihm_model_list._rowCount === 0) return { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any }; const { ihm_model_list, ihm_sphere_obj_site, ihm_gaussian_obj_site } = data; const modelIndex = Column.createIndexer(ihm_model_list.model_id); + const sphereData = getData(ihm_sphere_obj_site); + const sphereConformation = getSphereConformation(ihm_sphere_obj_site); + const sphereKeys = getCoarseKeys(sphereData, modelIndex, entities); + + const gaussianData = getData(ihm_gaussian_obj_site); + const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site); + const gaussianKeys = getCoarseKeys(gaussianData, modelIndex, entities); + return { - isDefined: true, - modelList: ihm_model_list, - spheres: getSpheres(ihm_sphere_obj_site, entities, modelIndex), - gaussians: getGaussians(ihm_gaussian_obj_site, entities, modelIndex) + hierarchy: { + isDefined: true, + models: ihm_model_list, + spheres: { ...sphereData, ...sphereKeys }, + gaussians: { ...gaussianData, ...gaussianKeys }, + }, + conformation: { + id: UUID.create(), + spheres: sphereConformation, + gaussians: gaussianConformation + } }; } -function getSpheres(data: mmCIF['ihm_sphere_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrainedHierarchy.Spheres { - const { Cartn_x, Cartn_y, Cartn_z, object_radius: radius, rmsf } = data; - const x = Cartn_x.toArray({ array: Float32Array }); - const y = Cartn_y.toArray({ array: Float32Array }); - const z = Cartn_z.toArray({ array: Float32Array }); - return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, radius, rmsf }; +function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphereConformation { + return { + x: data.Cartn_x.toArray({ array: Float32Array }), + y: data.Cartn_y.toArray({ array: Float32Array }), + z: data.Cartn_z.toArray({ array: Float32Array }), + radius: data.object_radius.toArray({ array: Float32Array }), + rmsf: data.rmsf.toArray({ array: Float32Array }) + }; } -function getGaussians(data: mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrainedHierarchy.Gaussians { - const { mean_Cartn_x, mean_Cartn_y, mean_Cartn_z, weight, covariance_matrix } = data; - const x = mean_Cartn_x.toArray({ array: Float32Array }); - const y = mean_Cartn_y.toArray({ array: Float32Array }); - const z = mean_Cartn_z.toArray({ array: Float32Array }); - return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, weight, covariance_matrix, matrix_space: mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space }; -} +function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGaussianConformation { + const matrix_space = mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space; + const covariance_matrix: Mat3[] = []; + const { covariance_matrix: cm } = data; -function getCommonColumns(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number) { - const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data; + for (let i = 0, _i = cm.rowCount; i < _i; i++) { + covariance_matrix[i] = Tensor.toMat3(matrix_space, cm.value(i)); + } return { - entityKey: Column.mapToArray(entity_id, id => entities.getEntityIndex(id), Int32Array), - modelKey: Column.mapToArray(model_id, modelIndex, Int32Array), - asym_id, - seq_id_begin, - seq_id_end + x: data.mean_Cartn_x.toArray({ array: Float32Array }), + y: data.mean_Cartn_y.toArray({ array: Float32Array }), + z: data.mean_Cartn_z.toArray({ array: Float32Array }), + weight: data.weight.toArray({ array: Float32Array }), + covariance_matrix }; } -export { coarseGrainedFromIHM } \ No newline at end of file +function getChainSegments(asym_id: Column<string>) { + const offsets = [0]; + for (let i = 1, _i = asym_id.rowCount; i < _i; i++) { + if (!asym_id.areValuesEqual(i - 1, i)) offsets[offsets.length] = i; + } + + return Segmentation.ofOffsets(offsets, Interval.ofBounds(0, asym_id.rowCount)); +} + +function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData { + const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data; + return { count: model_id.rowCount, entity_id, model_id, asym_id, seq_id_begin, seq_id_end, chainSegments: getChainSegments(asym_id) }; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts index a03d511f5..1817e1c22 100644 --- a/src/mol-model/structure/model/formats/mmcif/util.ts +++ b/src/mol-model/structure/model/formats/mmcif/util.ts @@ -16,9 +16,9 @@ export function findEntityIdByAsymId(model: Model, asymId: string) { } export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null) { - const { segmentMap, segments } = model.hierarchy.residueSegments + const { segmentMap, segments } = model.atomicHierarchy.residueSegments const idx = segmentMap[residueIndex] - const { label_atom_id, label_alt_id } = model.hierarchy.atoms; + const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms; for (let i = segments[idx], n = segments[idx + 1]; i <= n; ++i) { if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i; } diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index eb7d5024a..0a1725d48 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -9,10 +9,10 @@ import Format from './format' import Sequence from './properties/sequence' import { AtomicHierarchy, AtomicConformation } from './properties/atomic' import { ModelSymmetry } from './properties/symmetry' -import { CoarseGrainedHierarchy } from './properties/coarse-grained/hierarchy' +import { CoarseHierarchy, CoarseConformation } from './properties/coarse' import { Entities } from './properties/common'; -import from_gro from './formats/gro' +//import from_gro from './formats/gro' import from_mmCIF from './formats/mmcif' /** @@ -31,11 +31,11 @@ interface Model extends Readonly<{ entities: Entities, sequence: Sequence, - hierarchy: AtomicHierarchy, - atomSiteConformation: AtomicConformation, - coarseGrained: CoarseGrainedHierarchy, + atomicHierarchy: AtomicHierarchy, + atomicConformation: AtomicConformation, - atomCount: number, + coarseHierarchy: CoarseHierarchy, + coarseConformation: CoarseConformation }> { } { } @@ -43,22 +43,10 @@ interface Model extends Readonly<{ namespace Model { export function create(format: Format) { switch (format.kind) { - case 'gro': return from_gro(format); + //case 'gro': return from_gro(format); case 'mmCIF': return from_mmCIF(format); } } - // export function spatialLookup(model: Model): GridLookup { - // if (model['@spatialLookup']) return model['@spatialLookup']!; - // const lookup = GridLookup(model.conformation); - // model['@spatialLookup'] = lookup; - // return lookup; - // } - // export function bonds(model: Model): Bonds { - // if (model['@bonds']) return model['@bonds']!; - // const bonds = computeBonds(model); - // model['@bonds'] = bonds; - // return bonds; - // } } export default Model \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index 02ef4dd5d..fe50e990b 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -8,7 +8,6 @@ import { Column, Table } from 'mol-data/db' import { Segmentation } from 'mol-data/int' import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif' import { ElementSymbol} from '../../types' -import { Keys } from '../common'; export const AtomsSchema = { type_symbol: Column.Schema.Aliased<ElementSymbol>(mmCIF.atom_site.type_symbol), @@ -52,9 +51,21 @@ export interface AtomicSegments { chainSegments: Segmentation } -export interface AtomicKeys extends Keys { +export interface AtomicKeys { + // indicate whether the keys form an increasing sequence and within each chain, sequence numbers + // are in increasing order. + // monotonous sequences enable for example faster secondary structure assignment. + isMonotonous: boolean, + // assign a key to each residue index. - residueKey: Column<number>, + residueKey: ArrayLike<number>, + // assign a key to each chain index + chainKey: ArrayLike<number>, + // assigne a key to each chain index + // also index to the Entities table. + entityKey: ArrayLike<number>, + + findChainKey(entityId: string, label_asym_id: string): number findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number } diff --git a/src/mol-model/structure/model/properties/coarse-grained/conformation.ts b/src/mol-model/structure/model/properties/coarse-grained/conformation.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts b/src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts deleted file mode 100644 index 12dd042cd..000000000 --- a/src/mol-model/structure/model/properties/coarse-grained/hierarchy.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif' -import { Tensor } from 'mol-math/linear-algebra'; -import { Column } from 'mol-data/db'; - -interface CoarseGrainedHierarchy { - isDefined: boolean, - modelList: mmCIF['ihm_model_list'], - spheres: CoarseGrainedHierarchy.Spheres, - gaussians: CoarseGrainedHierarchy.Gaussians -} - -namespace CoarseGrainedHierarchy { - export const Empty: CoarseGrainedHierarchy = { isDefined: false } as any; - - export const enum ElementType { Sphere, Gaussian } - - export interface SiteBase { - asym_id: string, - seq_id_begin: number, - seq_id_end: number - } - - export interface Sphere extends SiteBase { - radius: number, - rmsf: number - } - - export interface Gaussian extends SiteBase { - weight: number, - covariance_matrix: Tensor.Data - } - - type Common = { - count: number, - x: ArrayLike<number>, - y: ArrayLike<number>, - z: ArrayLike<number>, - modelKey: ArrayLike<number>, - entityKey: ArrayLike<number> - } - - export type SitesBase = Common & { [P in keyof SiteBase]: Column<SiteBase[P]> } - export type Spheres = Common & { [P in keyof Sphere]: Column<Sphere[P]> } - export type Gaussians = Common & { matrix_space: Tensor.Space } & { [P in keyof Gaussian]: Column<Gaussian[P]> } -} - -export { CoarseGrainedHierarchy } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse.ts b/src/mol-model/structure/model/properties/coarse.ts new file mode 100644 index 000000000..d1fc3decf --- /dev/null +++ b/src/mol-model/structure/model/properties/coarse.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export * from './coarse/conformation' +export * from './coarse/hierarchy' \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse/conformation.ts b/src/mol-model/structure/model/properties/coarse/conformation.ts new file mode 100644 index 000000000..4298b80bd --- /dev/null +++ b/src/mol-model/structure/model/properties/coarse/conformation.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import UUID from 'mol-util/uuid' +import { Mat3 } from 'mol-math/linear-algebra'; + +export interface CoarseConformation { + id: UUID, + spheres: CoarseSphereConformation, + gaussians: CoarseGaussianConformation +} + +export interface CoarseSphereConformation { + x: ArrayLike<number>, + y: ArrayLike<number>, + z: ArrayLike<number>, + radius: ArrayLike<number>, + rmsf: ArrayLike<number> +} + +export interface CoarseGaussianConformation { + x: ArrayLike<number>, + y: ArrayLike<number>, + z: ArrayLike<number>, + weight: ArrayLike<number>, + covariance_matrix: ArrayLike<Mat3> +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse/hierarchy.ts b/src/mol-model/structure/model/properties/coarse/hierarchy.ts new file mode 100644 index 000000000..fedb463c9 --- /dev/null +++ b/src/mol-model/structure/model/properties/coarse/hierarchy.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif' +import { Column } from 'mol-data/db' +import { Segmentation } from 'mol-data/int'; + +export interface CoarsedElementKeys { + // indicate whether the keys form an increasing sequence and within each chain, sequence numbers + // are in increasing order. + // monotonous sequences enable for example faster secondary structure assignment. + isMonotonous: boolean, + + // assign a key to each element + chainKey: ArrayLike<number>, + // assign a key to each element, index to the Model.entities.data table + entityKey: ArrayLike<number>, + // assign a key to each element, index to the CoarseHierarchy.models table + modelKey: ArrayLike<number>, + + findChainKey(entityId: string, asym_id: string): number +} + +export interface CoarseElementData { + count: number, + entity_id: Column<string>, + model_id: Column<number>, + asym_id: Column<string>, + seq_id_begin: Column<number>, + seq_id_end: Column<number>, + + chainSegments: Segmentation +} + +export type CoarseElements = CoarsedElementKeys & CoarseElementData + +export interface CoarseHierarchy { + isDefined: boolean, + models: mmCIF['ihm_model_list'], + spheres: CoarseElements, + gaussians: CoarseElements +} + +export namespace CoarseHierarchy { + export const Empty: CoarseHierarchy = { isDefined: false } as any; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/common.ts b/src/mol-model/structure/model/properties/common.ts index e70712cbb..9dadbc95a 100644 --- a/src/mol-model/structure/model/properties/common.ts +++ b/src/mol-model/structure/model/properties/common.ts @@ -5,24 +5,8 @@ */ import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif' -import { Column } from 'mol-data/db'; export interface Entities { data: mmCIF['entity'], getEntityIndex(id: string): number -} - -export interface Keys { - // indicate whether the keys form an increasing sequence and within each chain, sequence numbers - // are in increasing order. - // monotonous sequences enable for example faster secondary structure assignment. - isMonotonous: boolean, - - // assign a key to each chain index - chainKey: Column<number>, - // assigne a key to each chain index - // also index to the Entities table. - entityKey: Column<number>, - - findChainKey(entityId: string, label_asym_id: string): number } \ No newline at end of file diff --git a/src/mol-model/structure/model/utils/hierarchy-keys.ts b/src/mol-model/structure/model/properties/utils/atomic-keys.ts similarity index 90% rename from src/mol-model/structure/model/utils/hierarchy-keys.ts rename to src/mol-model/structure/model/properties/utils/atomic-keys.ts index aabfe1b4a..1cfec2f03 100644 --- a/src/mol-model/structure/model/utils/hierarchy-keys.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-keys.ts @@ -4,10 +4,9 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Column } from 'mol-data/db' -import { AtomicData, AtomicSegments, AtomicKeys } from '../properties/atomic' +import { AtomicData, AtomicSegments, AtomicKeys } from '../atomic' import { Interval, Segmentation } from 'mol-data/int' -import { Entities } from '../properties/common'; +import { Entities } from '../common' function getResidueId(comp_id: string, seq_id: number, ins_code: string) { return `${comp_id} ${seq_id} ${ins_code}`; @@ -62,7 +61,7 @@ function missingEntity(k: string) { throw new Error(`Missing entity entry for entity id '${k}'.`); } -function create(data: AtomicData, entities: Entities, segments: AtomicSegments): AtomicKeys { +export function getAtomicKeys(data: AtomicData, entities: Entities, segments: AtomicSegments): AtomicKeys { const { chains, residues } = data; const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 }; @@ -110,12 +109,10 @@ function create(data: AtomicData, entities: Entities, segments: AtomicSegments): return { isMonotonous: isMonotonous && checkMonotonous(entityKey) && checkMonotonous(chainKey) && checkMonotonous(residueKey), - residueKey: Column.ofIntArray(residueKey), - chainKey: Column.ofIntArray(chainKey), - entityKey: Column.ofIntArray(entityKey), + residueKey: residueKey, + chainKey: chainKey, + entityKey: entityKey, findChainKey, findResidueKey }; -} - -export default create; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/utils/coarse-keys.ts b/src/mol-model/structure/model/properties/utils/coarse-keys.ts new file mode 100644 index 000000000..6ef72039a --- /dev/null +++ b/src/mol-model/structure/model/properties/utils/coarse-keys.ts @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Entities } from '../common'; +import { CoarseElementData, CoarsedElementKeys } from '../coarse'; + +function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) { + if (map.has(key)) return map.get(key)!; + const ret = counter.index++; + map.set(key, ret); + return ret; +} + +function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key: number) { + if (map.has(key)) return map.get(key)!; + const ret = new Map<string, number>(); + map.set(key, ret); + return ret; +} + +function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>) { + const getEntKey = entities.getEntityIndex; + const findChainKey: CoarsedElementKeys['findChainKey'] = (e, c) => { + let eKey = getEntKey(e); + if (eKey < 0) return -1; + const cm = chain.get(eKey)!; + if (!cm.has(c)) return -1; + return cm.get(c)!; + } + return { findChainKey }; +} + +function checkMonotonous(xs: ArrayLike<number>) { + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i] < xs[i - 1]) { + return false; + } + } + return true; +} + +function missingEntity(k: string) { + throw new Error(`Missing entity entry for entity id '${k}'.`); +} + +function missingModel(k: string) { + throw new Error(`Missing entity entry for model id '${k}'.`); +} + +export function getCoarseKeys(data: CoarseElementData, modelIndex: (id: number) => number, entities: Entities): CoarsedElementKeys { + const { model_id, entity_id, asym_id, count, chainSegments } = data; + + const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 }; + const chainKey = new Int32Array(count); + const entityKey = new Int32Array(count); + const modelKey = new Int32Array(count); + + for (let i = 0; i < count; i++) { + entityKey[i] = entities.getEntityIndex(entity_id.value(i)); + if (entityKey[i] < 0) missingEntity(entity_id.value(i)); + modelKey[i] = modelIndex(model_id.value(i)); + if (modelKey[i] < 0) missingModel('' + model_id.value(i)); + } + + for (let cI = 0; cI < chainSegments.count; cI++) { + const start = chainSegments.segments[cI], end = chainSegments.segments[cI + 1]; + const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]); + const key = getElementKey(map, asym_id.value(start), chainCounter); + for (let i = start; i < end; i++) chainKey[i] = key; + } + + const { findChainKey } = createLookUp(entities, chainMaps); + + return { + isMonotonous: checkMonotonous(entityKey) && checkMonotonous(chainKey), + chainKey: chainKey, + entityKey: entityKey, + modelKey: modelKey, + findChainKey + }; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/utils/guess-element.ts b/src/mol-model/structure/model/properties/utils/guess-element.ts similarity index 100% rename from src/mol-model/structure/model/utils/guess-element.ts rename to src/mol-model/structure/model/properties/utils/guess-element.ts diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts index ac9df8509..01445a1da 100644 --- a/src/mol-model/structure/query/generators.ts +++ b/src/mol-model/structure/query/generators.ts @@ -80,8 +80,8 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A const elements = unit.elements; builder.beginUnit(unit.id); - const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements); - const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements); + const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements); + const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements); while (chainsIt.hasNext) { const chainSegment = chainsIt.move(); l.element = OrderedSet.getAt(elements, chainSegment.start); @@ -174,8 +174,8 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group l.unit = unit; const elements = unit.elements; - const chainsIt = Segmentation.transientSegments(unit.model.hierarchy.chainSegments, elements); - const residuesIt = Segmentation.transientSegments(unit.model.hierarchy.residueSegments, elements); + const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements); + const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements); while (chainsIt.hasNext) { const chainSegment = chainsIt.move(); l.element = OrderedSet.getAt(elements, chainSegment.start); diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts index fc2a066ad..a4d4ad5b3 100644 --- a/src/mol-model/structure/query/properties.ts +++ b/src/mol-model/structure/query/properties.ts @@ -29,61 +29,61 @@ const atom = { x: Element.property(l => l.unit.conformation.x(l.element)), y: Element.property(l => l.unit.conformation.y(l.element)), z: Element.property(l => l.unit.conformation.z(l.element)), - id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomSiteConformation.atomId.value(l.element)), - occupancy: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomSiteConformation.occupancy.value(l.element)), - B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomSiteConformation.B_iso_or_equiv.value(l.element)), + id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)), + occupancy: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)), + B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)), // Hierarchy - type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.type_symbol.value(l.element)), - label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_atom_id.value(l.element)), - auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.auth_atom_id.value(l.element)), - label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.label_alt_id.value(l.element)), - pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.atoms.pdbx_formal_charge.value(l.element)), + type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)), + label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)), + auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)), + label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)), + pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)), // Derived - vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.hierarchy.atoms.type_symbol.value(l.element))), + vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))), } const residue = { - key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residueKey.value(l.unit.residueIndex[l.element])), - - group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])), - label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])), - auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])), - label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])), - auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])), - pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])) + key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]), + + group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])), + label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])), + auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])), + label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])), + auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])), + pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])) } const chain = { - key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chainKey.value(l.unit.chainIndex[l.element])), + key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]), - label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])), - auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])), - label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element])) + label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])), + auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])), + label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element])) } -const coarse_grained = { +const coarse = { key: atom.key, - modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.modelKey[l.element]), - entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.entityKey[l.element]), + modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.modelKey[l.element]), + entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]), x: atom.x, y: atom.y, z: atom.z, - asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.asym_id.value(l.element)), - seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.seq_id_begin.value(l.element)), - seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.sites.seq_id_end.value(l.element)), + asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)), + seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)), + seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)), - sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.sites.radius.value(l.element)), - sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.sites.rmsf.value(l.element)), + sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]), + sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]), - gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.sites.weight.value(l.element)), - gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.sites.covariance_matrix.value(l.element)) + gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]), + gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element]) } -function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.hierarchy.entityKey.value(l.unit.chainIndex[l.element]); } +function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; } const entity = { key: eK, @@ -112,7 +112,7 @@ const Properties = { chain, entity, unit, - coarse_grained + coarse } type Properties = typeof Properties diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index c2a2ecd38..efe4e8bde 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -13,6 +13,7 @@ import Element from './element' import Unit from './unit' import { StructureLookup3D } from './util/lookup3d'; import StructureSymmetry from './symmetry'; +import { CoarseElements } from '../model/properties/coarse'; class Structure { readonly unitMap: IntMap<Unit>; @@ -90,7 +91,7 @@ namespace Structure { } export function ofModel(model: Model): Structure { - const chains = model.hierarchy.chainSegments; + const chains = model.atomicHierarchy.chainSegments; const builder = new StructureBuilder(); for (let c = 0; c < chains.count; c++) { @@ -98,21 +99,27 @@ namespace Structure { builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements); } - const cs = model.coarseGrained; + const cs = model.coarseHierarchy; if (cs.isDefined) { if (cs.spheres.count > 0) { - const elements = SortedArray.ofBounds(0, cs.spheres.count); - builder.addUnit(Unit.Kind.Spheres, model, SymmetryOperator.Default, elements); + addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres); } if (cs.gaussians.count > 0) { - const elements = SortedArray.ofBounds(0, cs.spheres.count); - builder.addUnit(Unit.Kind.Gaussians, model, SymmetryOperator.Default, elements); + addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians); } } return builder.getStructure(); } + function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) { + const { chainSegments } = elements; + for (let cI = 0; cI < chainSegments.count; cI++) { + const elements = SortedArray.ofBounds(chainSegments.segments[cI], chainSegments.segments[cI + 1]); + builder.addUnit(kind, model, SymmetryOperator.Default, elements); + } + } + export class StructureBuilder { private units: Unit[] = []; diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 7ede3f8a7..4ea70f014 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -7,10 +7,10 @@ import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator' import { Model } from '../model' import { GridLookup3D, Lookup3D } from 'mol-math/geometry' -import { CoarseGrainedHierarchy } from '../model/properties/coarse-grained/hierarchy'; import { SortedArray } from 'mol-data/int'; import { idFactory } from 'mol-util/id-factory'; import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds' +import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse'; // A building block of a structure that corresponds to an atomic or a coarse grained representation // 'conveniently grouped together'. @@ -26,9 +26,9 @@ namespace Unit { export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit { switch (kind) { - case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation)); - case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, model.coarseGrained.spheres, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation)); - case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, model.coarseGrained.gaussians, elements, SymmetryOperator.createMapping(operator, model.atomSiteConformation)); + case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation)); + case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres)); + case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians)); } } @@ -78,13 +78,13 @@ namespace Unit { applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit { const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator); - return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomSiteConformation)); + return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation)); } private _lookup3d?: Lookup3D = void 0; get lookup3d() { if (this._lookup3d) return this._lookup3d; - const { x, y, z } = this.model.atomSiteConformation; + const { x, y, z } = this.model.atomicConformation; this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements }); return this._lookup3d; } @@ -103,62 +103,64 @@ namespace Unit { this.elements = elements; this.conformation = conformation; - this.residueIndex = model.hierarchy.residueSegments.segmentMap; - this.chainIndex = model.hierarchy.chainSegments.segmentMap; + this.residueIndex = model.atomicHierarchy.residueSegments.segmentMap; + this.chainIndex = model.atomicHierarchy.chainSegments.segmentMap; } } - // Coarse grained representations. - export interface CoarseBase<S extends CoarseGrainedHierarchy.SitesBase> extends Base { - readonly sites: S - } - - class Coarse<S extends CoarseGrainedHierarchy.SitesBase> implements CoarseBase<S> { - readonly kind: Kind; + class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base { + readonly kind: K; readonly id: number; readonly invariantId: number; readonly elements: SortedArray; readonly model: Model; readonly conformation: SymmetryOperator.ArrayMapping; - readonly sites: S; + + readonly coarseElements: CoarseElements; + readonly coarseConformation: C; getChild(elements: SortedArray): Unit { if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */; - return createCoarse(this.id, this.invariantId, this.model, this.kind, this.sites, elements, this.conformation); + return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation); } applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit { const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator); - return createCoarse(id, this.invariantId, this.model, this.kind, this.sites, this.elements, SymmetryOperator.createMapping(op, this.sites)); + return createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements())); } private _lookup3d?: Lookup3D = void 0; get lookup3d() { if (this._lookup3d) return this._lookup3d; - const { x, y, z } = this.sites; - // TODO: support sphere radius + const { x, y, z } = this.getCoarseElements(); + // TODO: support sphere radius? this._lookup3d = GridLookup3D({ x, y, z, indices: this.elements }); return this._lookup3d; } - constructor(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) { + private getCoarseElements() { + return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians; + } + + constructor(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) { this.kind = kind; this.id = id; this.invariantId = invariantId; this.model = model; this.elements = elements; this.conformation = conformation; - this.sites = sites; + this.coarseElements = kind === Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians; + this.coarseConformation = (kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians) as C; } } - function createCoarse<S extends CoarseGrainedHierarchy.SitesBase>(id: number, invariantId: number, model: Model, kind: Kind, sites: S, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit { - return new Coarse(id, invariantId, model, kind, sites, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */; + function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit { + return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */; } - export interface Spheres extends CoarseBase<CoarseGrainedHierarchy.Spheres> { kind: Kind.Spheres } - export interface Gaussians extends CoarseBase<CoarseGrainedHierarchy.Gaussians> { kind: Kind.Gaussians } + export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { } + export class Gaussians extends Coarse<Kind.Gaussians, CoarseGaussianConformation> { } } export default Unit; \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts index 2d8f9af02..9cd4a9ed1 100644 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts @@ -109,11 +109,11 @@ function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[], function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds { const MAX_RADIUS = 3; - const { x, y, z } = unit.model.atomSiteConformation; + const { x, y, z } = unit.model.atomicConformation; const atomCount = unit.elements.length; const { elements: atoms, residueIndex } = unit; - const { type_symbol, label_atom_id, label_alt_id } = unit.model.hierarchy.atoms; - const { label_comp_id } = unit.model.hierarchy.residues; + const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms; + const { label_comp_id } = unit.model.atomicHierarchy.residues; const query3d = unit.lookup3d; const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.create(unit.model) : void 0 diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 4070cca0b..92831adec 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -371,7 +371,7 @@ export namespace PropertyAccess { // return; console.log('bs', baseline(models[0])); - console.log('sp', sumProperty(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); + console.log('sp', sumProperty(structures[0], l => l.unit.model.atomicConformation.atomId.value(l.element))); //console.log(sumPropertySegmented(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); //console.log(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.element)); @@ -418,7 +418,7 @@ export namespace PropertyAccess { console.log(Selection.structureCount(q2r)); //console.log(q1(structures[0])); - const col = models[0].atomSiteConformation.atomId.value; + const col = models[0].atomicConformation.atomId.value; const suite = new B.Suite(); suite //.add('test q', () => q1(structures[0])) -- GitLab