atomic.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /**
  2. * Copyright (c) 2017-2021 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 { Column, Table } from '../../../mol-data/db';
  8. import { Interval, Segmentation } from '../../../mol-data/int';
  9. import { toDatabase } from '../../../mol-io/reader/cif/schema';
  10. import { SymmetryOperator } from '../../../mol-math/geometry';
  11. import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
  12. import { ChainIndex, ElementIndex } from '../../../mol-model/structure';
  13. import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
  14. import { Model } from '../../../mol-model/structure/model/model';
  15. import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
  16. import { Entities } from '../../../mol-model/structure/model/properties/common';
  17. import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
  18. import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
  19. import { ElementSymbol } from '../../../mol-model/structure/model/types';
  20. import { UUID } from '../../../mol-util/uuid';
  21. import { ModelFormat } from '../../format';
  22. import { MmcifFormat } from '../mmcif';
  23. import { AtomSite } from './schema';
  24. function findHierarchyOffsets(atom_site: AtomSite) {
  25. if (atom_site._rowCount === 0) return { residues: [], chains: [] };
  26. const start = 0, end = atom_site._rowCount;
  27. const residues = [start as ElementIndex], chains = [start as ElementIndex];
  28. const { label_entity_id, label_asym_id, label_seq_id, auth_seq_id, pdbx_PDB_ins_code } = atom_site;
  29. for (let i = start + 1 as ElementIndex; i < end; i++) {
  30. const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !label_asym_id.areValuesEqual(i - 1, i);
  31. const newResidue = newChain
  32. || !label_seq_id.areValuesEqual(i - 1, i)
  33. || !auth_seq_id.areValuesEqual(i - 1, i)
  34. || !pdbx_PDB_ins_code.areValuesEqual(i - 1, i);
  35. // not checking label_comp_id to allow for MICROHETEROGENEITY
  36. if (newResidue) residues[residues.length] = i as ElementIndex;
  37. if (newChain) chains[chains.length] = i as ElementIndex;
  38. }
  39. return { residues, chains };
  40. }
  41. function substUndefinedColumn<T extends Table<any>>(table: T, a: keyof T, b: keyof T) {
  42. if (!(table as any)[a].isDefined) {
  43. (table as any)[a] = (table as any)[b];
  44. }
  45. if (!(table as any)[b].isDefined) {
  46. (table as any)[b] = (table as any)[a];
  47. }
  48. }
  49. function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData {
  50. const atoms = Table.ofColumns(AtomsSchema, {
  51. type_symbol: Column.ofArray({ array: Column.mapToArray(atom_site.type_symbol, ElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
  52. label_atom_id: atom_site.label_atom_id,
  53. auth_atom_id: atom_site.auth_atom_id,
  54. label_alt_id: atom_site.label_alt_id,
  55. label_comp_id: atom_site.label_comp_id,
  56. auth_comp_id: atom_site.auth_comp_id,
  57. pdbx_formal_charge: atom_site.pdbx_formal_charge
  58. });
  59. const residues = Table.view(atom_site, ResiduesSchema, offsets.residues);
  60. const chains = Table.view(atom_site, ChainsSchema, offsets.chains);
  61. if (!residues.label_seq_id.isDefined) {
  62. const seqIds = new Int32Array(residues.label_seq_id.rowCount);
  63. const { residues: residueOffsets, chains: chainOffsets } = offsets;
  64. let cI = 0;
  65. let seqId = 0;
  66. for (let i = 0, il = seqIds.length; i < il; ++i) {
  67. if (residueOffsets[i] >= chainOffsets[cI + 1]) {
  68. cI += 1;
  69. seqId = 0;
  70. }
  71. seqIds[i] = ++seqId; // start id on one
  72. }
  73. residues.label_seq_id = Column.ofIntArray(seqIds);
  74. }
  75. // Optimize the numeric columns
  76. Table.columnToArray(residues, 'label_seq_id', Int32Array);
  77. Table.columnToArray(residues, 'auth_seq_id', Int32Array);
  78. // Fix possibly missing auth_/label_ columns
  79. substUndefinedColumn(atoms, 'label_atom_id', 'auth_atom_id');
  80. substUndefinedColumn(atoms, 'label_comp_id', 'auth_comp_id');
  81. substUndefinedColumn(residues, 'label_seq_id', 'auth_seq_id');
  82. substUndefinedColumn(chains, 'label_asym_id', 'auth_asym_id');
  83. return { atoms, residues, chains, atomSourceIndex: sourceIndex };
  84. }
  85. function getConformation(atom_site: AtomSite): AtomicConformation {
  86. return {
  87. id: UUID.create22(),
  88. atomId: atom_site.id,
  89. occupancy: atom_site.occupancy.isDefined ? atom_site.occupancy : Column.ofConst(1, atom_site._rowCount, Column.Schema.float),
  90. B_iso_or_equiv: atom_site.B_iso_or_equiv,
  91. xyzDefined: atom_site.Cartn_x.isDefined && atom_site.Cartn_y.isDefined && atom_site.Cartn_z.isDefined,
  92. x: atom_site.Cartn_x.toArray({ array: Float32Array }),
  93. y: atom_site.Cartn_y.toArray({ array: Float32Array }),
  94. z: atom_site.Cartn_z.toArray({ array: Float32Array }),
  95. };
  96. }
  97. function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
  98. return Table.areEqual(a.chains, b.chains)
  99. && Table.areEqual(a.residues, b.residues)
  100. && Table.areEqual(a.atoms, b.atoms);
  101. }
  102. function createChainOperatorMappingAndSubstituteNames(hierarchy: AtomicData, format: ModelFormat) {
  103. const mapping = new Map<ChainIndex, SymmetryOperator>();
  104. if (!MmcifFormat.is(format)) return mapping;
  105. const { molstar_atom_site_operator_mapping: entries } = toDatabase(AtomSiteOperatorMappingSchema, format.data.frame);
  106. if (entries._rowCount === 0) return mapping;
  107. const labelMap = new Map<string, { name: string, operator: SymmetryOperator }>();
  108. const authMap = new Map<string, string>();
  109. for (let i = 0; i < entries._rowCount; i++) {
  110. const assembly: SymmetryOperator['assembly'] = entries.assembly_operator_id.valueKind(i) === Column.ValueKind.Present
  111. ? { id: entries.assembly_id.value(i), operList: [], operId: entries.assembly_operator_id.value(i) }
  112. : void 0;
  113. const operator = SymmetryOperator.create(entries.operator_name.value(i), Mat4.identity(), {
  114. assembly,
  115. spgrOp: entries.symmetry_operator_index.valueKind(i) === Column.ValueKind.Present ? entries.symmetry_operator_index.value(i) : void 0,
  116. hkl: Vec3.ofArray(entries.symmetry_hkl.value(i)),
  117. ncsId: entries.ncs_id.value(i)
  118. });
  119. const suffix = entries.suffix.value(i);
  120. const label = entries.label_asym_id.value(i);
  121. labelMap.set(`${label}${suffix}`, { name: label, operator });
  122. const auth = entries.auth_asym_id.value(i);
  123. authMap.set(`${auth}${suffix}`, auth);
  124. }
  125. const { label_asym_id, auth_asym_id } = hierarchy.chains;
  126. const mappedLabel: string[] = new Array(label_asym_id.rowCount);
  127. const mappedAuth: string[] = new Array(label_asym_id.rowCount);
  128. for (let i = 0 as ChainIndex; i < label_asym_id.rowCount; i++) {
  129. const label = label_asym_id.value(i), auth = auth_asym_id.value(i);
  130. if (!labelMap.has(label)) {
  131. mappedLabel[i] = label;
  132. mappedAuth[i] = auth;
  133. continue;
  134. }
  135. const { name, operator } = labelMap.get(label)!;
  136. mapping.set(i, operator);
  137. mappedLabel[i] = name;
  138. mappedAuth[i] = authMap.get(auth) || auth;
  139. }
  140. hierarchy.chains.label_asym_id = Column.ofArray({ array: mappedLabel, valueKind: hierarchy.chains.label_asym_id.valueKind, schema: hierarchy.chains.label_asym_id.schema });
  141. hierarchy.chains.auth_asym_id = Column.ofArray({ array: mappedAuth, valueKind: hierarchy.chains.auth_asym_id.valueKind, schema: hierarchy.chains.auth_asym_id.schema });
  142. return mapping;
  143. }
  144. function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
  145. const hierarchyOffsets = findHierarchyOffsets(atom_site);
  146. const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
  147. const chainOperatorMapping = createChainOperatorMappingAndSubstituteNames(hierarchyData, format);
  148. if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
  149. return {
  150. sameAsPrevious: true,
  151. hierarchy: previous.atomicHierarchy,
  152. chainOperatorMapping
  153. };
  154. }
  155. const hierarchySegments: AtomicSegments = {
  156. residueAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)),
  157. chainAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)),
  158. };
  159. const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
  160. const derived = getAtomicDerivedData(hierarchyData, hierarchySegments, index, chemicalComponentMap);
  161. const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
  162. return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
  163. }
  164. export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
  165. const { sameAsPrevious, hierarchy, chainOperatorMapping } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, format, previous);
  166. const conformation = getConformation(atom_site);
  167. return { sameAsPrevious, hierarchy, conformation, chainOperatorMapping };
  168. }