@datagrok/sequence-translator 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/detectors.js +24 -0
  3. package/dist/455.js +2 -0
  4. package/dist/455.js.map +1 -0
  5. package/dist/package-test.js +1 -1
  6. package/dist/package-test.js.map +1 -1
  7. package/dist/package.js +1 -1
  8. package/dist/package.js.map +1 -1
  9. package/files/polytool-rules/rules_example.json +2 -2
  10. package/files/samples/HELM.csv +6 -0
  11. package/files/samples/cyclized.csv +4 -3
  12. package/files/samples/cyclized_MSA.csv +5 -0
  13. package/package.json +13 -13
  14. package/src/apps/common/model/oligo-toolkit-package.ts +23 -4
  15. package/src/apps/common/view/components/molecule-img.ts +2 -2
  16. package/src/apps/pattern/model/data-manager.ts +9 -9
  17. package/src/apps/translator/view/ui.ts +8 -8
  18. package/src/consts.ts +12 -0
  19. package/src/global.d.ts +13 -0
  20. package/src/package-test.ts +3 -0
  21. package/src/package.ts +32 -5
  22. package/src/polytool/pt-conversion.ts +395 -81
  23. package/src/polytool/pt-dialog.ts +29 -13
  24. package/src/polytool/pt-enumeration-helm-dialog.ts +79 -34
  25. package/src/polytool/pt-enumeration-helm.ts +12 -8
  26. package/src/polytool/pt-placeholders-breadth-input.ts +4 -4
  27. package/src/polytool/pt-placeholders-input.ts +6 -6
  28. package/src/polytool/pt-unrule-dialog.ts +7 -3
  29. package/src/polytool/pt-unrule.ts +4 -3
  30. package/src/polytool/types.ts +4 -4
  31. package/src/tests/polytool-chain-from-notation-tests.ts +108 -18
  32. package/src/tests/polytool-chain-parse-notation-tests.ts +100 -0
  33. package/src/tests/polytool-convert-tests.ts +102 -25
  34. package/src/tests/polytool-detectors-custom-notation-test.ts +43 -0
  35. package/src/tests/polytool-enumerate-breadth-tests.ts +4 -4
  36. package/src/tests/toAtomicLevel-tests.ts +1 -1
  37. package/src/tests/utils/detect-macromolecule-utils.ts +64 -0
  38. package/src/tests/{utils.ts → utils/index.ts} +2 -2
  39. package/src/utils/context-menu.ts +2 -5
  40. package/src/utils/cyclized.ts +88 -0
  41. package/src/utils/dimerized.ts +10 -0
@@ -2,98 +2,149 @@ import * as grok from 'datagrok-api/grok';
2
2
  import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
- import {PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
6
- import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
5
+ import wu from 'wu';
6
+
7
+ import {cleanupHelmSymbol} from '@datagrok-libraries/bio/src/helm/utils';
8
+ import {HelmTypes, PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
9
+ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
7
10
  import {IMonomerLib, IMonomerLibBase, Monomer, MonomerLibData, RGroup} from '@datagrok-libraries/bio/src/types';
8
11
  import {RDModule, RDMol, RDReaction, MolList, RDReactionResult} from '@datagrok-libraries/chem-meta/src/rdkit-api';
9
- import {HELM_REQUIRED_FIELD, HELM_RGROUP_FIELDS} from '@datagrok-libraries/bio/src/utils/const';
12
+ import {HELM_REQUIRED_FIELD as REQ, HELM_OPTIONAL_FIELDS as OPT, HELM_RGROUP_FIELDS, HELM_OPTIONAL_FIELDS} from '@datagrok-libraries/bio/src/utils/const';
10
13
  import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
14
+ import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
15
+ import {HelmAtom, HelmBio, HelmMol, HelmType, JSDraw2ModuleType, OrgType} from '@datagrok-libraries/bio/src/helm/types';
16
+ import {IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
11
17
 
12
18
  import {Rules, RuleLink, RuleReaction} from './pt-rules';
13
19
  import {InvalidReactionError, MonomerNotFoundError} from './types';
14
- import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
20
+
15
21
  import {_package} from '../package';
16
22
 
17
- export const RULES_DIMER = '(#2)';
18
- export const RULES_HETERODIMER = '($2)';
23
+ declare const JSDraw2: JSDraw2ModuleType;
24
+ declare const org: OrgType;
25
+
26
+ type Linkage = {
27
+ fChain: number,
28
+ sChain: number,
29
+ /** Continuous 1-based numbering */ fMonomer: number,
30
+ /** Continuous 1-based numbering */ sMonomer: number,
31
+ fR: number,
32
+ sR: number
33
+ }
19
34
 
20
- // function addCommonTags(col: DG.Column): void {
21
- // col.semType = DG.SEMTYPE.MACROMOLECULE;
22
- // col.setTag('aligned', ALIGNMENT.SEQ);
23
- // col.setTag('alphabet', ALPHABET.PT);
24
- // }
35
+ type PolyToolBio = HelmBio & { i: number, j: number };
25
36
 
26
37
  export class Chain {
27
- linkages: { fChain: number, sChain: number, fMonomer: number, sMonomer: number, fR: number, sR: number }[];
38
+ linkages: Linkage[];
28
39
  monomers: string[][];
40
+ mol: HelmMol;
29
41
 
30
42
  constructor(
31
43
  monomers: string[][],
32
- linkages: { fChain: number, sChain: number, fMonomer: number, sMonomer: number, fR: number, sR: number }[]) {
44
+ linkages: Linkage[],
45
+ mol: HelmMol) {
33
46
  this.linkages = linkages;
34
47
  this.monomers = monomers;
48
+ this.mol = mol;
35
49
  }
36
50
 
37
- static fromHelm(helm: string) {
38
- const fragmentation = helm.split('$');
51
+ /** Parse harmonized sequence (template) from pseudo helm */
52
+ static parseHelm(helm: string, helmHelper: IHelmHelper) {
53
+ const ea = /(\w+\{.*\})\$(.*)\$(.*)\$(.*)\$/g.exec(helm)!;
54
+ // const fragmentation = helm.split('$');
55
+ const fragmentation = [ea[1], ea[2], ea[3], ea[4]];
56
+
39
57
  const rawFragments = fragmentation[0].split('|');
40
58
  const rawLinkages = fragmentation[1].split('|');
41
59
 
42
60
  const monomers = new Array<Array<string>>(rawFragments.length);
43
- const linkages: {
44
- fChain: number,
45
- sChain: number,
46
- fMonomer: number,
47
- sMonomer: number,
48
- fR: number,
49
- sR: number
50
- }[] = [];
61
+ const linkages: Linkage[] = [];
62
+
63
+ const resHwe = helmHelper.createHelmWebEditor();
64
+ const resMol = resHwe.editor.m;
51
65
 
66
+ let counter = 0;
67
+ const p = new JSDraw2.Point(0, 0);
52
68
  //HELM parsing
53
69
  for (let i = 0; i < rawFragments.length; i++) {
54
70
  const idxStart = rawFragments[i].indexOf('{');
55
71
  const idxEnd = rawFragments[i].indexOf('}');
56
72
 
57
- monomers[i] = rawFragments[i].slice(idxStart + 1, idxEnd).split('.');
73
+ monomers[i] = rawFragments[i].slice(idxStart + 1, idxEnd).split('.').map((s) => cleanupHelmSymbol(s));
74
+ for (let j = 0; j < monomers[i].length; j++) {
75
+ const elem = monomers[i][j];
76
+ const bio: PolyToolBio = {type: HelmTypes.AA, i: i, j: j, continuousId: counter};
77
+ const atom = new JSDraw2.Atom<HelmType>(p, elem, bio);
78
+ resMol.addAtom(atom);
79
+
80
+ if (j !== 0) {
81
+ const atom1 = resMol.atoms[counter - 1];
82
+ const atom2 = resMol.atoms[counter];
83
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
84
+ bond.r1 = 2;
85
+ bond.r2 = 1;
86
+ resMol.addBond(bond);
87
+ }
88
+
89
+ counter++;
90
+ p.x += JSDraw2.Editor.BONDLENGTH; // Inspired by HELMWebEditor
91
+ }
92
+ p.y += 4 * JSDraw2.Editor.BONDLENGTH; // Inspired by HELMWebEditor
58
93
  }
59
94
 
60
95
  //HELM parsing
61
96
  for (let i = 0; i < rawLinkages.length; i++) {
62
97
  if (rawLinkages[i] !== '' && rawLinkages[i] !== 'V2.0') {
63
98
  const rawData = rawLinkages[i].split(',');
64
- const seq1 = (rawData[0].replace('PEPTIDE', '') as unknown as number) - 1;
65
- const seq2 = (rawData[1].replace('PEPTIDE', '') as unknown as number) - 1;
66
- const rawDataConnctions = rawData[2].split('-');
67
- const rawDataConnction1 = rawDataConnctions[0].split(':');
68
- const rawDataConnction2 = rawDataConnctions[1].split(':');
99
+ const fChainIdx = parseInt(rawData[0].replace('PEPTIDE', '')) - 1;
100
+ const sChainIdx = parseInt(rawData[1].replace('PEPTIDE', '')) - 1;
101
+ const rawDataConnections = rawData[2].split('-');
102
+ const rawDataConnection1 = rawDataConnections[0].split(':');
103
+ const rawDataConnection2 = rawDataConnections[1].split(':');
69
104
 
70
105
  linkages.push({
71
- fChain: seq1,
72
- sChain: seq2,
73
- fMonomer: rawDataConnction1[0] as unknown as number,
74
- sMonomer: rawDataConnction2[0] as unknown as number,
75
- fR: rawDataConnction1[1].replace('R', '') as unknown as number,
76
- sR: rawDataConnction2[1].replace('R', '') as unknown as number,
106
+ fChain: fChainIdx,
107
+ sChain: sChainIdx,
108
+ fMonomer: getOuterIdx(parseInt(rawDataConnection1[0]), fChainIdx, monomers),
109
+ sMonomer: getOuterIdx(parseInt(rawDataConnection2[0]), sChainIdx, monomers),
110
+ fR: parseInt(rawDataConnection1[1].replace('R', '')),
111
+ sR: parseInt(rawDataConnection2[1].replace('R', '')),
77
112
  });
78
113
  }
79
114
  }
80
115
 
81
- return new Chain(monomers, linkages);
116
+ for (let i = 0; i < linkages.length; i++) {
117
+ const atom1 = resMol.atoms[linkages[i].fMonomer - 1];
118
+ const atom2 = resMol.atoms[linkages[i].sMonomer - 1];
119
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
120
+ bond.r1 = linkages[i].fR;
121
+ bond.r2 = linkages[i].sR;
122
+ resMol.addBond(bond);
123
+ }
124
+
125
+ return new Chain(monomers, linkages, resMol);
126
+ }
127
+
128
+ /** Get macromolecule from harmonized sequence (template) */
129
+ applyRules(rules: Rules): Chain {
130
+ // Clone this
131
+ const resMonomers: string[][] = this.monomers.map((mL) => [...mL]);
132
+ const resLinkages: Linkage[] = [...this.linkages];
133
+ const resMol: HelmMol = this.mol.clone();
134
+
135
+ throw new Error('not implemented');
136
+
137
+ const chain = new Chain(resMonomers, resLinkages, resMol);
138
+ return chain;
82
139
  }
83
140
 
84
- static fromNotation(sequence: string, rules: Rules): Chain {
141
+ /** @deprecated Use {@link parseNotation} and {@link applyRules} instead. */
142
+ static fromNotation(sequence: string, rules: Rules, helmHelper: IHelmHelper): Chain {
85
143
  const heterodimerCode = rules.heterodimerCode;
86
144
  const homodimerCode = rules.homodimerCode;
87
- const mainFragments: string[] = [];
88
145
 
89
- const linkages: {
90
- fChain: number,
91
- sChain: number,
92
- fMonomer: number,
93
- sMonomer: number,
94
- fR: number,
95
- sR: number
96
- }[] = [];
146
+ const mainFragments: string[] = [];
147
+ const linkages: Linkage[] = [];
97
148
 
98
149
  //NOTICE: this works only with simple single heterodimers
99
150
  const heterodimeric = heterodimerCode !== null ? sequence.split(`(${rules.heterodimerCode!})`) : '';
@@ -164,6 +215,11 @@ export class Chain {
164
215
 
165
216
  const monomersAll: string[][] = [];
166
217
 
218
+ const resHwe = helmHelper.createHelmWebEditor();
219
+ const resMol = resHwe.editor.m;
220
+
221
+ let counter = 0;
222
+ const p = new JSDraw2.Point(0, 0);
167
223
  for (let i = 0; i < monomers.length; i++) {
168
224
  const linkedPositions = this.getLinkedPositions(monomers[i], rules.reactionRules);
169
225
  const [monomersCycled, allPos1, allPos2, ruleN] =
@@ -172,14 +228,41 @@ export class Chain {
172
228
  if (allPos1.length >= 1) {
173
229
  const ch1 = new Array<string>(allPos2[0] - 1);
174
230
  const ch2 = new Array<string>(monomersCycled.length - allPos2[0]);
175
- for (let j = 0; j < allPos2[0] - 1; j++)
176
- ch1[j] = monomersCycled[j];
177
-
178
- for (let j = allPos2[0]; j < monomersCycled.length; j++)
179
- ch2[j - allPos2[0]] = monomersCycled[j];
231
+ for (let j = 0; j < allPos2[0] - 1; j++) {
232
+ const elem = ch1[j] = monomersCycled[j];
233
+ const bio: PolyToolBio = {type: HelmTypes.AA, i: i, j: j, continuousId: counter};
234
+ const atom: HelmAtom = new JSDraw2.Atom<HelmType>(p, elem, bio);
235
+ resMol.addAtom(atom);
236
+
237
+ if (j > 0) {
238
+ const atom1 = resMol.atoms[counter - 1];
239
+ const atom2 = resMol.atoms[counter];
240
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
241
+ bond.r1 = 2;
242
+ bond.r2 = 1;
243
+ resMol.addBond(bond);
244
+ }
245
+ counter++;
246
+ }
180
247
 
248
+ for (let j = allPos2[0]; j < monomersCycled.length; j++) {
249
+ const elem = ch2[j - allPos2[0]] = monomersCycled[j];
250
+ const bio: PolyToolBio = {type: HelmTypes.AA, i: i, j: j, continuousId: counter};
251
+ const atom: HelmAtom = new JSDraw2.Atom<HelmType>(p, elem, bio);
252
+ resMol.addAtom(atom);
253
+
254
+ if (j > allPos2[0]) {
255
+ const atom1 = resMol.atoms[counter - 1];
256
+ const atom2 = resMol.atoms[counter];
257
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
258
+ bond.r1 = 2;
259
+ bond.r2 = 1;
260
+ resMol.addBond(bond);
261
+ }
262
+ counter++;
263
+ }
181
264
 
182
- ch1[allPos1[0] - 1] = rules.reactionRules[ruleN[0]].name;
265
+ resMol.atoms[allPos1[0] - 1].elem = ch1[allPos1[0] - 1] = rules.reactionRules[ruleN[0]].name;
183
266
 
184
267
  for (let j = 0; j < linkages.length; j++) {
185
268
  if (linkages[j].fMonomer > allPos2[0]) {
@@ -219,11 +302,116 @@ export class Chain {
219
302
  monomersAll.push(ch1);
220
303
  monomersAll.push(ch2);
221
304
  } else {
305
+ for (let j = 0; j < monomers[i].length; j++) {
306
+ const elem = monomers[i][j];
307
+ const bio: PolyToolBio = {type: HelmTypes.AA, i: i, j: j, continuousId: counter};
308
+ const atom: HelmAtom = new JSDraw2.Atom<HelmType>(p, elem, bio);
309
+ resMol.addAtom(atom);
310
+
311
+ if (j > 0) {
312
+ const atom1 = resMol.atoms[counter - 1];
313
+ const atom2 = resMol.atoms[counter];
314
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
315
+ bond.r1 = 2;
316
+ bond.r2 = 1;
317
+ resMol.addBond(bond);
318
+ }
319
+ counter++;
320
+ }
222
321
  monomersAll.push(monomers[i]);
223
322
  }
224
323
  }
225
324
 
226
- const chain = new Chain(monomersAll, linkages);
325
+ for (const l of linkages) {
326
+ const atom1 = resMol.atoms[l.fMonomer - 1];
327
+ const atom2 = resMol.atoms[l.sMonomer - 1];
328
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
329
+ bond.r1 = l.fR;
330
+ bond.r2 = l.sR;
331
+ resMol.addBond(bond);
332
+ }
333
+
334
+ const chain = new Chain(monomersAll, linkages, resMol);
335
+ return chain;
336
+ }
337
+
338
+ /** Parse harmonized sequence notation (template) */
339
+ static parseNotation(sequence: string, helmHelper: IHelmHelper): Chain {
340
+ const mainFragments: string[][] = [];
341
+
342
+ const linkages: Linkage[] = [];
343
+
344
+ const resHwe = helmHelper.createHelmWebEditor();
345
+ const resMol = resHwe.editor.m;
346
+
347
+ const rxp = /(\(.\d+\))?\{[^\}]*\}/g;
348
+ const seqs: string [] = [];
349
+ seqs.push(sequence.replaceAll(rxp, ''));
350
+
351
+ //const l = (rxpRes?.length) ?? -1;
352
+
353
+ const matches = sequence.matchAll(rxp);
354
+ //const rxpRes = rxp.exec(sequence);
355
+ for (const m of matches) {
356
+ const str = m![0];
357
+ if (str)
358
+ seqs.push(str);
359
+ }
360
+
361
+ let counter = 0;
362
+ for (let i = 0; i < seqs.length; i++) {
363
+ const splMonomers = seqs[i].split('-');
364
+ const monomers: string [] = new Array<string>(splMonomers.length);
365
+ let spmCount: number = 0;
366
+ for (let j = 0; j < splMonomers.length; j++) {
367
+ const monomer = splMonomers[j].replace('{', '').replace('}', '');
368
+ if (monomer !== '') {
369
+ monomers[j] = monomer;
370
+ counter++;
371
+ spmCount++;
372
+ } else {
373
+ linkages.push({fChain: i, sChain: i + 1, fMonomer: counter, sMonomer: counter + 1, fR: 1, sR: 1});
374
+ }
375
+ }
376
+ mainFragments.push(monomers.slice(0, spmCount));
377
+ }
378
+
379
+ counter = 0;
380
+ const p = new JSDraw2.Point(0, 0);
381
+ for (let i = 0; i < mainFragments.length; i++) {
382
+ for (let j = 0; j < mainFragments[i].length; j++) {
383
+ if (!!mainFragments[i][j]) {
384
+ const elem = mainFragments[i][j];
385
+ const bio: PolyToolBio = {type: HelmTypes.AA, i: i, j: j, continuousId: counter};
386
+ const atom = new JSDraw2.Atom<HelmType>(p, elem, bio);
387
+ resMol.addAtom(atom);
388
+
389
+ if (j !== 0) {
390
+ const atom1 = resMol.atoms[counter - 1];
391
+ const atom2 = resMol.atoms[counter];
392
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
393
+ bond.r1 = 2;
394
+ bond.r2 = 1;
395
+ resMol.addBond(bond);
396
+ }
397
+
398
+ counter++;
399
+ p.x += JSDraw2.Editor.BONDLENGTH; // Inspired by HELMWebEditor
400
+ }
401
+ p.y += 4 * JSDraw2.Editor.BONDLENGTH; // Inspired by HELMWebEditor
402
+ }
403
+ }
404
+
405
+ for (let i = 0; i < linkages.length; i++) {
406
+ const atom1 = resMol.atoms[linkages[i].fMonomer - 1];
407
+ const atom2 = resMol.atoms[linkages[i].sMonomer - 1];
408
+ const bond = new JSDraw2.Bond<HelmType>(atom1, atom2);
409
+ bond.r1 = linkages[i].fR;
410
+ bond.r2 = linkages[i].sR;
411
+ resMol.addBond(bond);
412
+ }
413
+
414
+ const chain = new Chain(mainFragments, linkages, resMol);
227
415
  return chain;
228
416
  }
229
417
 
@@ -233,17 +421,17 @@ export class Chain {
233
421
  let idx1 = 0;
234
422
  let idx2 = 0;
235
423
  loop1:
236
- for (let i = 0; i < this.monomers.length; i++) {
237
- loop2:
238
- for (let j = 0; j < this.monomers[i].length; j++) {
239
- if (counter == changeNumber) {
240
- idx1 = i;
241
- idx2 = j;
242
- break loop1;
243
- }
244
- counter++;
245
- }
424
+ for (let i = 0; i < this.monomers.length; i++) {
425
+ loop2:
426
+ for (let j = 0; j < this.monomers[i].length; j++) {
427
+ if (counter == changeNumber) {
428
+ idx1 = i;
429
+ idx2 = j;
430
+ break loop1;
431
+ }
432
+ counter++;
246
433
  }
434
+ }
247
435
 
248
436
  const previous = this.monomers[idx1][idx2];
249
437
 
@@ -254,6 +442,12 @@ export class Chain {
254
442
  return res;
255
443
  }
256
444
 
445
+ /** Gets harmonized sequence (template) pseudo helm */
446
+ getNotationHelm(): string {
447
+ return this.getHelm();
448
+ }
449
+
450
+ /** Gets harmonized sequence (template) pseudo helm */
257
451
  getHelm(): string {
258
452
  let helm = '';
259
453
  for (let i = 0; i < this.monomers.length; i++) {
@@ -277,16 +471,75 @@ export class Chain {
277
471
  if (i > 0)
278
472
  helm += '|';
279
473
  helm += `PEPTIDE${this.linkages[i].fChain + 1},PEPTIDE${this.linkages[i].sChain + 1},`;
280
- helm += `${this.linkages[i].fMonomer}:R${this.linkages[i].fR}-`;
281
- helm += `${this.linkages[i].sMonomer}:R${this.linkages[i].sR}`;
474
+
475
+ helm += `${getInnerIdx(this.linkages[i].fMonomer - 1, this.monomers)[0] + 1}:R${this.linkages[i].fR}-`;
476
+ helm += `${getInnerIdx(this.linkages[i].sMonomer - 1, this.monomers)[0] + 1}:R${this.linkages[i].sR}`;
282
477
  }
283
478
 
284
- helm += '$$$';
479
+ helm += '$$$' + 'V2.0';
285
480
  return helm;
286
481
  }
287
482
 
288
- getNotation(rules: Rules): string {
289
- return 'not implemented';
483
+ getNotation(): string {
484
+ const atoms = this.mol.atoms;
485
+ const bonds = this.mol.bonds;
486
+ const chains: number[] = [];
487
+ const specialBonds: number[] = [];
488
+ for (let i = 0; i < bonds.length!; i++) {
489
+ //@ts-ignore
490
+ if (bonds[i].a1.bio.i !== bonds[i].a2.bio.i)
491
+ specialBonds.push(i);
492
+ }
493
+
494
+ for (let i = 0; i < atoms.length!; i++) {
495
+ //@ts-ignore
496
+ const atomChain = atoms[i].bio?.i;
497
+ if (atomChain + 1 > chains.length)
498
+ chains.push(1);
499
+ else
500
+ chains[atomChain]++;
501
+ }
502
+
503
+ const simpleChains: string[][] = new Array(chains.length);
504
+ let counter = 0;
505
+ for (let i = 0; i < chains.length!; i++) {
506
+ const simpleChain: string[] = new Array(chains[i]);
507
+ for (let j = 0; j < chains[i]; j++) {
508
+ simpleChain[j] = atoms[counter].elem;
509
+ counter++;
510
+ }
511
+
512
+ simpleChains[i] = simpleChain;
513
+ }
514
+
515
+ let res = '';
516
+ for (let i = 0; i < simpleChains.length; i++) {
517
+ let chainAdd = '';
518
+
519
+ for (let j = 0; j < simpleChains[i].length; j++)
520
+ chainAdd += `${j == 0 ? '' : '-'}${simpleChains[i][j]}`;
521
+
522
+ if (i !== 0) {
523
+ const rxp = /(\(.\d+\))/;
524
+ const match = chainAdd.match(rxp);
525
+ chainAdd = chainAdd.replace(match?.[0]!, '');
526
+ const group = match ? match?.[0]! : '';
527
+ chainAdd = `${group}{${chainAdd}}`;
528
+ } else {
529
+ if (simpleChains.length > 1) {
530
+ //@ts-ignore
531
+ const firstAtomLinks = bonds[specialBonds[0]].a1.bio.i == 0 && bonds[specialBonds[0]].a1.bio.j == 0;
532
+ //@ts-ignore
533
+ const secondAtomLinks = bonds[specialBonds[0]].a2.bio.i == 1 && bonds[specialBonds[0]].a1.bio.j == 0;
534
+ if (firstAtomLinks && secondAtomLinks)
535
+ chainAdd += '-';
536
+ }
537
+ }
538
+
539
+ res += chainAdd;
540
+ }
541
+
542
+ return res;
290
543
  }
291
544
 
292
545
  protected static getLinkedPositions(monomers: string[], rules: RuleLink[] | RuleReaction []):
@@ -390,15 +643,48 @@ export class Chain {
390
643
 
391
644
  return [monomers, allPos1, allPos2, rule];
392
645
  }
646
+
647
+ public check(throwError: boolean = false): string[] {
648
+ const errors: string[] = [];
649
+
650
+ const chainsMonomerCount = this.monomers.map((ch) => ch.length).reduce((acc, curr) => acc + curr, 0);
651
+ if (this.mol.atoms.length !== chainsMonomerCount)
652
+ errors.push(`The mol atoms count ${this.mol.atoms.length} does not match ` +
653
+ `the total number ${chainsMonomerCount} of chains' monomers.`);
654
+
655
+ const internalBondsCount = this.monomers.map((ch) => ch.length - 1).reduce((acc, curr) => acc + curr, 0);
656
+ const chainsBondCount = internalBondsCount + this.linkages.length;
657
+ if (this.mol.bonds.length !== chainsBondCount)
658
+ errors.push(`The mol bonds count ${this.mol.bonds.length} does not match ` +
659
+ `the total number ${chainsBondCount} in- and inter-chain linkages.`);
660
+
661
+ let counter: number = 0;
662
+ for (let spIdx = 0; spIdx < this.monomers.length; ++spIdx) {
663
+ const chain = this.monomers[spIdx];
664
+ for (let mIntIdx = 0; mIntIdx < chain.length; ++mIntIdx) {
665
+ try {
666
+ const m = chain[mIntIdx];
667
+ const a = this.mol.atoms[counter];
668
+ if (a.bio!.continuousId !== counter)
669
+ errors.push(`Atom #${counter} has incorrect .bio.continuousId: ${a.bio!.continuousId}.`);
670
+ if (a.elem !== m)
671
+ errors.push(`Atom #${counter} elem: '${a.elem}' does not match chain monomer: '${m}'.`);
672
+ } finally { counter++; }
673
+ }
674
+ }
675
+ if (throwError && errors.length > 0)
676
+ throw new Error(`Chain errors:\n${errors.map((e) => ` ${e}`).join('\n')}`);
677
+ return errors;
678
+ }
393
679
  }
394
680
 
395
681
  /** The main PolyTool convert engine. Returns list of Helms. Covered with tests. */
396
- export function doPolyToolConvert(sequences: string[], rules: Rules): string[] {
682
+ export function doPolyToolConvert(sequences: string[], rules: Rules, helmHelper: IHelmHelper): string[] {
397
683
  const helms = new Array<string>(sequences.length);
398
684
  for (let i = 0; i < sequences.length; i++) {
399
685
  try {
400
686
  if (sequences[i] == null) { helms[i] = ''; } else {
401
- const chain = Chain.fromNotation(sequences[i], rules);
687
+ const chain = Chain.fromNotation(sequences[i], rules, helmHelper);
402
688
  helms[i] = chain.getHelm();
403
689
  }
404
690
  } catch (err: any) {
@@ -517,18 +803,24 @@ export function getNewMonomer(rdkit: RDModule, mLib: IMonomerLib, rule: RuleReac
517
803
  const groups: RGroup[] = getNewGroups(monomer1!, monomer2!);
518
804
 
519
805
  const resMonomer: Monomer = {
520
- [HELM_REQUIRED_FIELD.SYMBOL]: monomerName,
521
- [HELM_REQUIRED_FIELD.NAME]: monomerName,
522
- [HELM_REQUIRED_FIELD.MOLFILE]: molBlock,
523
- [HELM_REQUIRED_FIELD.AUTHOR]: '',
524
- [HELM_REQUIRED_FIELD.ID]: 0,
525
- [HELM_REQUIRED_FIELD.RGROUPS]: groups,
526
- [HELM_REQUIRED_FIELD.SMILES]: '',
527
- [HELM_REQUIRED_FIELD.POLYMER_TYPE]: 'PEPTIDE',
528
- [HELM_REQUIRED_FIELD.MONOMER_TYPE]: 'Backbone',
529
- [HELM_REQUIRED_FIELD.CREATE_DATE]: null,
806
+ [REQ.SYMBOL]: monomerName,
807
+ [REQ.NAME]: monomerName,
808
+ [REQ.MOLFILE]: molBlock,
809
+ [REQ.AUTHOR]: '',
810
+ [REQ.ID]: 0,
811
+ [REQ.RGROUPS]: groups,
812
+ [REQ.SMILES]: '',
813
+ [REQ.POLYMER_TYPE]: 'PEPTIDE',
814
+ [REQ.MONOMER_TYPE]: 'Backbone',
815
+ [REQ.CREATE_DATE]: null,
816
+
817
+ // // @ts-ignore
818
+ // lib: {source: 'Reaction'},
530
819
  };
531
820
 
821
+ resMonomer[OPT.META] = Object.assign(resMonomer[OPT.META] ?? {},
822
+ {'colors': {'default': {line: '#2083D5', text: '#2083D5', background: '#F2F2F5'}}});
823
+
532
824
  return [monomerName, resMonomer];
533
825
  }
534
826
 
@@ -545,6 +837,28 @@ export async function getOverriddenLibrary(rules: Rules): Promise<IMonomerLibBas
545
837
  }
546
838
 
547
839
  const overrideMonomerLibData: MonomerLibData = {[PolymerTypes.PEPTIDE]: argLib};
548
- const overriddenMonomerLib = systemMonomerLib.override(overrideMonomerLibData);
840
+ const overriddenMonomerLib = systemMonomerLib.override(overrideMonomerLibData,
841
+ 'ST-PT-reactions.' + wu.repeat(1).map(() => Math.floor((Math.random() * 36)).toString(36)).take(4).toArray().join(''));
549
842
  return overriddenMonomerLib;
550
843
  }
844
+
845
+ /** Gets 0-based in-index (simple polymer) of out-index (continuous) {@link idx} */
846
+ export function getInnerIdx(outIdx: number, monomers: string[][]): [number, number] {
847
+ // let prevSpCount = 0;
848
+ // for (let spI = 0; spI < monomers.length && idx >= (prevSpCount + monomers[spI].length); ++spI)
849
+ // prevSpCount += monomers[spI].length;
850
+ // return idx - prevSpCount;
851
+ let inIdx = outIdx;
852
+ let spIdx: number;
853
+ for (spIdx = 0; spIdx < monomers.length && inIdx >= monomers[spIdx].length; ++spIdx)
854
+ inIdx -= monomers[spIdx].length;
855
+ return [inIdx, spIdx];
856
+ }
857
+
858
+ /** Gets 0-based out-index of 0-based in-index {@link inIdx} monomer of simple polymer {@link spIdx} */
859
+ export function getOuterIdx(inIdx: number, spIdx: number, monomers: string[][]): number {
860
+ let outIdx = 0;
861
+ for (let i = 0; i < spIdx; ++i)
862
+ outIdx += monomers[i].length;
863
+ return outIdx + inIdx;
864
+ }