encoder.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  5. */
  6. import { StringBuilder } from '../../../mol-util';
  7. import { Category } from '../cif/encoder';
  8. import { getCategoryInstanceData } from '../cif/encoder/util';
  9. import { LigandEncoder } from '../ligand-encoder';
  10. // specification: http://c4.cabrillo.edu/404/ctfile.pdf
  11. // SDF wraps MOL and allows for multiple molecules per file as well as additional properties
  12. export class MolEncoder extends LigandEncoder {
  13. _writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
  14. // use separate builder because we still need to write Counts and Bonds line
  15. const ctab = StringBuilder.create();
  16. const bonds = StringBuilder.create();
  17. // write Atom block and gather data for Bonds and Charges
  18. const { instance, source } = getCategoryInstanceData(category, context);
  19. // write header
  20. const name = this.getName(instance, source);
  21. // 3rd lines must be present and can contain comments
  22. StringBuilder.writeSafe(this.builder, `${name}\n ${this.encoder}\n\n`);
  23. const atomMap = this.componentAtomData.entries.get(name)!;
  24. const bondMap = this.componentBondData.entries.get(name)!;
  25. let bondCount = 0;
  26. let chiral = false;
  27. // traverse once to determine all actually present atoms
  28. const atoms = this.getAtoms(instance, source);
  29. atoms.forEach((atom1, label_atom_id1) => {
  30. const { index: i1 } = atom1;
  31. const { charge, stereo_config } = atomMap.map.get(label_atom_id1)!;
  32. StringBuilder.writePadLeft(ctab, atom1.Cartn_x.toFixed(4), 10);
  33. StringBuilder.writePadLeft(ctab, atom1.Cartn_y.toFixed(4), 10);
  34. StringBuilder.writePadLeft(ctab, atom1.Cartn_z.toFixed(4), 10);
  35. StringBuilder.whitespace1(ctab);
  36. StringBuilder.writePadRight(ctab, atom1.type_symbol, 2);
  37. StringBuilder.writeSafe(ctab, ' 0');
  38. StringBuilder.writeIntegerPadLeft(ctab, this.mapCharge(charge), 3);
  39. StringBuilder.writeSafe(ctab, ' 0 0 0 0 0 0 0 0 0 0\n');
  40. if (stereo_config !== 'N') chiral = true;
  41. // no data for metal ions
  42. if (!bondMap?.map) return;
  43. bondMap.map.get(label_atom_id1)!.forEach((bond, label_atom_id2) => {
  44. const atom2 = atoms.get(label_atom_id2);
  45. if (!atom2) return;
  46. const { index: i2, type_symbol: type_symbol2 } = atom2;
  47. if (i1 < i2 && !this.skipHydrogen(type_symbol2)) {
  48. const { order } = bond;
  49. StringBuilder.writeIntegerPadLeft(bonds, i1 + 1, 3);
  50. StringBuilder.writeIntegerPadLeft(bonds, i2 + 1, 3);
  51. StringBuilder.writeIntegerPadLeft(bonds, order, 3);
  52. StringBuilder.writeSafe(bonds, ' 0 0 0 0\n');
  53. bondCount++;
  54. }
  55. });
  56. });
  57. // write counts line
  58. StringBuilder.writeIntegerPadLeft(this.builder, atoms.size, 3);
  59. StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
  60. StringBuilder.writeSafe(this.builder, ` 0 0 ${chiral ? 1 : 0} 0 0 0 0 0 0\n`);
  61. StringBuilder.writeSafe(this.builder, StringBuilder.getString(ctab));
  62. StringBuilder.writeSafe(this.builder, StringBuilder.getString(bonds));
  63. StringBuilder.writeSafe(this.builder, 'M END\n');
  64. }
  65. private mapCharge(raw: number): number {
  66. // 0 = uncharged or value other than these, 1 = +3, 2 = +2, 3 = +1, 4 = doublet radical, 5 = -1, 6 = -2, 7 = -3
  67. switch (raw) {
  68. case 3: return 1;
  69. case 2: return 2;
  70. case 1: return 3;
  71. case -1: return 5;
  72. case -2: return 6;
  73. case -3: return 7;
  74. default: return 0;
  75. }
  76. }
  77. protected writeFullCategory<Ctx>(sb: StringBuilder, category: Category<Ctx>, context?: Ctx) {
  78. const { instance, source } = getCategoryInstanceData(category, context);
  79. const fields = instance.fields;
  80. const src = source[0];
  81. const data = src.data;
  82. const it = src.keys();
  83. const key = it.move();
  84. for (let _f = 0; _f < fields.length; _f++) {
  85. const f = fields[_f]!;
  86. StringBuilder.writeSafe(sb, `> <${category.name}.${f.name}>\n`);
  87. const val = f.value(key, data, 0);
  88. StringBuilder.writeSafe(sb, val as string);
  89. StringBuilder.writeSafe(sb, '\n\n');
  90. }
  91. }
  92. encode() {
  93. // write meta-information, do so after ctab
  94. if (this.error || this.metaInformation) {
  95. StringBuilder.writeSafe(this.builder, StringBuilder.getString(this.meta));
  96. }
  97. // terminate file (needed for SDF only)
  98. if (!!this.terminator) {
  99. StringBuilder.writeSafe(this.builder, `${this.terminator}\n`);
  100. }
  101. this.encoded = true;
  102. }
  103. constructor(encoder: string, metaInformation: boolean, hydrogens: boolean, readonly terminator: string = '') {
  104. super(encoder, metaInformation, hydrogens);
  105. if (metaInformation && !terminator) {
  106. throw new Error('meta-information cannot be written for MOL files');
  107. }
  108. }
  109. }