bonds.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /**
  2. * Copyright (c) 2017-2018 MolQL 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 Model from '../../model'
  8. import Bonds from '../../properties/bonds'
  9. import { BondType } from '../../types'
  10. import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
  11. import { Column } from 'mol-data/db'
  12. export class StructConn implements Bonds.StructConn {
  13. private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
  14. private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
  15. private static _resKey(rA: number, rB: number) {
  16. if (rA < rB) return `${rA}-${rB}`;
  17. return `${rB}-${rA}`;
  18. }
  19. private getResiduePairIndex() {
  20. if (this._residuePairIndex) return this._residuePairIndex;
  21. this._residuePairIndex = new Map();
  22. for (const e of this.entries) {
  23. const ps = e.partners;
  24. const l = ps.length;
  25. for (let i = 0; i < l - 1; i++) {
  26. for (let j = i + i; j < l; j++) {
  27. const key = StructConn._resKey(ps[i].residueIndex, ps[j].residueIndex);
  28. if (this._residuePairIndex.has(key)) {
  29. this._residuePairIndex.get(key)!.push(e);
  30. } else {
  31. this._residuePairIndex.set(key, [e]);
  32. }
  33. }
  34. }
  35. }
  36. return this._residuePairIndex;
  37. }
  38. private getAtomIndex() {
  39. if (this._atomIndex) return this._atomIndex;
  40. this._atomIndex = new Map();
  41. for (const e of this.entries) {
  42. for (const p of e.partners) {
  43. const key = p.atomIndex;
  44. if (this._atomIndex.has(key)) {
  45. this._atomIndex.get(key)!.push(e);
  46. } else {
  47. this._atomIndex.set(key, [e]);
  48. }
  49. }
  50. }
  51. return this._atomIndex;
  52. }
  53. private static _emptyEntry = [];
  54. getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> {
  55. return this.getResiduePairIndex().get(StructConn._resKey(residueAIndex, residueBIndex)) || StructConn._emptyEntry;
  56. }
  57. getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> {
  58. return this.getAtomIndex().get(atomIndex) || StructConn._emptyEntry;
  59. }
  60. constructor(public entries: StructConn.Entry[]) {
  61. }
  62. }
  63. export namespace StructConn {
  64. export interface Entry extends Bonds.StructConnEntry {
  65. distance: number,
  66. order: number,
  67. flags: number,
  68. partners: { residueIndex: number, atomIndex: number, symmetry: string }[]
  69. }
  70. type StructConnType =
  71. | 'covale'
  72. | 'covale_base'
  73. | 'covale_phosphate'
  74. | 'covale_sugar'
  75. | 'disulf'
  76. | 'hydrog'
  77. | 'metalc'
  78. | 'mismat'
  79. | 'modres'
  80. | 'saltbr'
  81. export function create(model: Model): StructConn | undefined {
  82. if (model.sourceData.kind !== 'mmCIF') return
  83. const { struct_conn } = model.sourceData.data;
  84. if (!struct_conn._rowCount) return void 0;
  85. const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn;
  86. const p1 = {
  87. label_asym_id: struct_conn.ptnr1_label_asym_id,
  88. label_comp_id: struct_conn.ptnr1_label_comp_id,
  89. label_seq_id: struct_conn.ptnr1_label_seq_id,
  90. label_atom_id: struct_conn.ptnr1_label_atom_id,
  91. label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id,
  92. ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code,
  93. symmetry: struct_conn.ptnr1_symmetry
  94. };
  95. const p2: typeof p1 = {
  96. label_asym_id: struct_conn.ptnr2_label_asym_id,
  97. label_comp_id: struct_conn.ptnr2_label_comp_id,
  98. label_seq_id: struct_conn.ptnr2_label_seq_id,
  99. label_atom_id: struct_conn.ptnr2_label_atom_id,
  100. label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id,
  101. ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code,
  102. symmetry: struct_conn.ptnr2_symmetry
  103. };
  104. const _p = (row: number, ps: typeof p1) => {
  105. if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
  106. const asymId = ps.label_asym_id.value(row)
  107. const residueIndex = model.hierarchy.findResidueKey(
  108. findEntityIdByAsymId(model, asymId),
  109. ps.label_comp_id.value(row),
  110. asymId,
  111. ps.label_seq_id.value(row),
  112. ps.ins_code.value(row)
  113. );
  114. if (residueIndex < 0) return void 0;
  115. const atomName = ps.label_atom_id.value(row);
  116. // turns out "mismat" records might not have atom name value
  117. if (!atomName) return void 0;
  118. const atomIndex = findAtomIndexByLabelName(model, residueIndex, atomName, ps.label_alt_id.value(row));
  119. if (atomIndex < 0) return void 0;
  120. return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) || '1_555' };
  121. }
  122. const _ps = (row: number) => {
  123. const ret = [];
  124. let p = _p(row, p1);
  125. if (p) ret.push(p);
  126. p = _p(row, p2);
  127. if (p) ret.push(p);
  128. return ret;
  129. }
  130. const entries: StructConn.Entry[] = [];
  131. for (let i = 0; i < struct_conn._rowCount; i++) {
  132. const partners = _ps(i);
  133. if (partners.length < 2) continue;
  134. const type = conn_type_id.value(i)! as StructConnType;
  135. const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
  136. let flags = BondType.Flag.None;
  137. let order = 1;
  138. switch (orderType) {
  139. case 'sing': order = 1; break;
  140. case 'doub': order = 2; break;
  141. case 'trip': order = 3; break;
  142. case 'quad': order = 4; break;
  143. }
  144. switch (type) {
  145. case 'covale':
  146. case 'covale_base':
  147. case 'covale_phosphate':
  148. case 'covale_sugar':
  149. case 'modres':
  150. flags = BondType.Flag.Covalent;
  151. break;
  152. case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Sulfide; break;
  153. case 'hydrog': flags = BondType.Flag.Hydrogen; break;
  154. case 'metalc': flags = BondType.Flag.MetallicCoordination; break;
  155. case 'saltbr': flags = BondType.Flag.Ion; break;
  156. }
  157. entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners });
  158. }
  159. return new StructConn(entries);
  160. }
  161. }
  162. export class ComponentBondInfo implements Bonds.ComponentBondInfo {
  163. entries: Map<string, ComponentBondInfo.Entry> = new Map();
  164. newEntry(id: string) {
  165. let e = new ComponentBondInfo.Entry(id);
  166. this.entries.set(id, e);
  167. return e;
  168. }
  169. }
  170. export namespace ComponentBondInfo {
  171. export class Entry implements Bonds.ComponentBondInfoEntry {
  172. map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
  173. add(a: string, b: string, order: number, flags: number, swap = true) {
  174. let e = this.map.get(a);
  175. if (e !== void 0) {
  176. let f = e.get(b);
  177. if (f === void 0) {
  178. e.set(b, { order, flags });
  179. }
  180. } else {
  181. let map = new Map<string, { order: number, flags: number }>();
  182. map.set(b, { order, flags });
  183. this.map.set(a, map);
  184. }
  185. if (swap) this.add(b, a, order, flags, false);
  186. }
  187. constructor(public id: string) {
  188. }
  189. }
  190. export function create(model: Model): ComponentBondInfo | undefined {
  191. if (model.sourceData.kind !== 'mmCIF') return
  192. const { chem_comp_bond } = model.sourceData.data;
  193. if (!chem_comp_bond._rowCount) return void 0;
  194. let info = new ComponentBondInfo();
  195. const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond;
  196. let entry = info.newEntry(comp_id.value(0)!);
  197. for (let i = 0; i < rowCount; i++) {
  198. const id = comp_id.value(i)!;
  199. const nameA = atom_id_1.value(i)!;
  200. const nameB = atom_id_2.value(i)!;
  201. const order = value_order.value(i)!;
  202. const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
  203. if (entry.id !== id) {
  204. entry = info.newEntry(id);
  205. }
  206. let flags: number = BondType.Flag.Covalent;
  207. let ord = 1;
  208. if (aromatic) flags |= BondType.Flag.Aromatic;
  209. switch (order.toLowerCase()) {
  210. case 'doub':
  211. case 'delo':
  212. ord = 2;
  213. break;
  214. case 'trip': ord = 3; break;
  215. case 'quad': ord = 4; break;
  216. }
  217. entry.add(nameA, nameB, ord, flags);
  218. }
  219. return info;
  220. }
  221. }