@datagrok/sequence-translator 1.9.10 → 1.9.12

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.
@@ -1,12 +1,19 @@
1
+ /* eslint-disable max-len */
1
2
  import * as DG from 'datagrok-api/dg';
2
3
  import * as grok from 'datagrok-api/grok';
3
4
  import * as ui from 'datagrok-api/ui';
4
- import {getMonomerPairs, getRules, Rules} from './pt-rules';
5
+ import {getMonomerPairs, getRules, LinkRuleRowArgs, Rules} from './pt-rules';
5
6
  import {_package, PackageFunctions} from '../../package';
6
7
  import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
7
8
  import {doPolyToolConvert} from './pt-conversion';
8
9
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
9
10
  import {RuleCards} from './pt-rule-cards';
11
+ import {getOverriddenLibrary} from './pt-synthetic';
12
+ import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
13
+ import {helmToMol} from './pt-atomic';
14
+ import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
15
+ import {getSeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
16
+ import {getReactionEditor, ReactionEditorProps} from './rule-reaction-editor';
10
17
 
11
18
 
12
19
  const TAB_LINKS = 'Links';
@@ -24,15 +31,27 @@ export class RulesManager {
24
31
  heteroDimerInput: DG.InputBase;
25
32
 
26
33
  linkCards: RuleCards[];
34
+ addLinkRulesFunc: (rowObj: LinkRuleRowArgs) => void;
35
+ addSynthRulesFunc: (rowObj: ReactionEditorProps) => void;
27
36
 
28
37
  // every rule set will have its editor instance
29
38
  private static instances: Record<string, RulesManager> = {};
30
39
 
31
40
  protected constructor(rules: Rules, fileName: string) {
32
41
  this.rules = rules;
33
- this.linkRuleDataFrame = this.rules.getLinkRulesDf();
34
-
35
- this.synthRuleDataFrame = this.rules.getSynthesisRulesDf();
42
+ const linksRes = this.rules.getLinkRulesDf();
43
+ this.linkRuleDataFrame = linksRes.res;
44
+ this.addLinkRulesFunc = linksRes.addNewRow;
45
+
46
+ const synthRes = this.rules.getSynthesisRulesDf();
47
+ this.addSynthRulesFunc = (r) => {
48
+ synthRes.addNewRow(r);
49
+ this.rules.setSynthesisRules(synthRes.df);
50
+ this.synthRuleDataFrame = synthRes.df;
51
+ this.substituteReactionGridDataFrame?.();
52
+ this.save();
53
+ };
54
+ this.synthRuleDataFrame = synthRes.df;
36
55
  this.fileName = fileName;
37
56
 
38
57
  const homoValue = this.rules.homodimerCode ? this.rules.homodimerCode : '';
@@ -166,23 +185,57 @@ export class RulesManager {
166
185
 
167
186
  const initCol = DG.Column.fromStrings('Monomers', seqs);
168
187
  const helmCol = DG.Column.fromStrings('Helm', helms);
169
-
188
+ const df = DG.DataFrame.fromColumns([
189
+ initCol, helmCol
190
+ ]);
170
191
  initCol.semType = DG.SEMTYPE.MACROMOLECULE;
192
+ const rdKitModule = await getRdKitModule();
193
+ const seqHelper = await getSeqHelper();
194
+ // initialize seqHandler for this column
171
195
  PackageFunctions.applyNotationProviderForCyclized(initCol, '-');
196
+ initCol.tags[DG.TAGS.CELL_RENDERER] = 'Sequence';
172
197
 
173
198
  helmCol.semType = DG.SEMTYPE.MACROMOLECULE;
174
199
  helmCol.meta.units = NOTATION.HELM;
175
200
  helmCol.setTag(DG.TAGS.CELL_RENDERER, 'helm');
176
- return DG.DataFrame.fromColumns([
177
- initCol, helmCol
178
- ]).plot.grid();
201
+
202
+ const lib = await getOverriddenLibrary(this.rules);
203
+ const resHelmColTemp = helmCol.temp;
204
+ resHelmColTemp[MmcrTemps.overriddenLibrary] = lib;
205
+ helmCol.temp = resHelmColTemp;
206
+
207
+ const resMolCol = await helmToMol(helmCol, seqs,
208
+ isLinear, true, false, false, lib, rdKitModule, seqHelper);
209
+ resMolCol.name = `molfile(sequence)`;
210
+ resMolCol.semType = DG.SEMTYPE.MOLECULE;
211
+ df.columns.add(resMolCol);
212
+
213
+ return df.plot.grid();
179
214
  }
180
215
 
216
+ private substituteReactionGridDataFrame: (() => void) | null = null;
217
+
181
218
  async getForm() {
182
- inputsTabControl: DG.TabControl;
219
+ const gridOptions: Partial<DG.IGridSettings> = {showAddNewRowIcon: false, allowEdit: false, rowHeight: 60};
220
+
221
+ const linksGrid = this.linkRuleDataFrame.plot.grid(gridOptions);
222
+ linksGrid.onCellDoubleClick.subscribe(() => {
223
+ if (!linksGrid.dataFrame || linksGrid.dataFrame.currentRowIdx == -1 || linksGrid.dataFrame.currentRowIdx == undefined)
224
+ return;
225
+ const idx = linksGrid.dataFrame.currentRowIdx;
226
+ const editArgs: LinkRuleRowArgs = {
227
+ row: idx,
228
+ code: this.linkRuleDataFrame.get('code', idx),
229
+ firstMonomers: this.linkRuleDataFrame.get('firstMonomers', idx),
230
+ secondMonomers: this.linkRuleDataFrame.get('secondMonomers', idx),
231
+ firstLinkingGroup: this.linkRuleDataFrame.get('firstLinkingGroup', idx),
232
+ secondLinkingGroup: this.linkRuleDataFrame.get('secondLinkingGroup', idx),
233
+ };
234
+ this.getAddNewLinkRuleDialog(editArgs);
235
+ });
183
236
 
184
237
  const linksGridDiv = this.createGridDiv('Rules',
185
- this.linkRuleDataFrame.plot.grid({showAddNewRowIcon: true}),
238
+ linksGrid,
186
239
  'specification for monomers to link and linking positions');
187
240
  const linkExamples = this.createGridDiv('Examples', await this.getLinkExamplesGrid(),
188
241
  'specification for monomers to link and linking positions');
@@ -205,7 +258,7 @@ export class RulesManager {
205
258
  this.linkRuleDataFrame.currentRowIdx = 0;
206
259
  this.linkRuleDataFrame.onCurrentRowChanged.subscribe(async () => {
207
260
  const idx = this.linkRuleDataFrame.currentRowIdx;
208
- if (idx !== -1) {
261
+ if (idx !== -1 && idx != undefined) {
209
262
  ui.empty(gridDiv);
210
263
  gridDiv.append(ui.splitV([
211
264
  ui.box(
@@ -214,17 +267,37 @@ export class RulesManager {
214
267
  ),
215
268
  this.linkCards[idx].root,
216
269
  ]));
270
+ this.linkCards[idx].render();
271
+ await this.linkCards[idx].reset();
217
272
  }
218
-
219
- this.linkCards[idx].render();
220
- await this.linkCards[idx].reset();
221
273
  });
222
274
 
223
275
  const links = ui.splitH([linksGridDiv, gridDiv], null, true);
276
+ const synthesisGrid = this.synthRuleDataFrame.plot.grid({showAddNewRowIcon: false, allowEdit: false, rowHeight: 130});
277
+ synthesisGrid.onCellDoubleClick.subscribe(() => {
278
+ if (!synthesisGrid.dataFrame || synthesisGrid.dataFrame.currentRowIdx == -1 || synthesisGrid.dataFrame.currentRowIdx == undefined)
279
+ return;
280
+ const idx = synthesisGrid.dataFrame.currentRowIdx;
281
+ const editArgs: ReactionEditorProps = {
282
+ rowIndex: idx,
283
+ code: this.synthRuleDataFrame.get('code', idx),
284
+ firstMonomers: this.synthRuleDataFrame.get('firstMonomers', idx).split(',').map((s: string) => s.trim()).filter((s: string) => s),
285
+ secondMonomers: this.synthRuleDataFrame.get('secondMonomers', idx).split(',').map((s: string) => s.trim()).filter((s: string) => s),
286
+ resultMonomerName: this.synthRuleDataFrame.get('name', idx),
287
+ firstReactantSmiles: this.synthRuleDataFrame.get('firstReactant', idx),
288
+ secondReactantSmiles: this.synthRuleDataFrame.get('secondReactant', idx),
289
+ productSmiles: this.synthRuleDataFrame.get('product', idx),
290
+ };
291
+ getReactionEditor((props: ReactionEditorProps) => this.addSynthRulesFunc(props), editArgs);
292
+ });
293
+ const reactionsGridDiv = this.createGridDiv('Rules', synthesisGrid);
224
294
 
225
- const reactionsGridDiv = this.createGridDiv('Rules',
226
- this.synthRuleDataFrame.plot.grid({showAddNewRowIcon: true}));
227
- const reactionExamples = this.createGridDiv('Examples', await this.getReactionExamplesGrid());
295
+ const reactionExamplesGrid = await this.getReactionExamplesGrid();
296
+ const reactionExamples = this.createGridDiv('Examples', reactionExamplesGrid);
297
+ this.substituteReactionGridDataFrame = async () => {
298
+ const newGrid = await this.getReactionExamplesGrid();
299
+ reactionExamplesGrid.dataFrame = newGrid.dataFrame;
300
+ };
228
301
  reactionsGridDiv.style.width = '50%';
229
302
  reactionExamples.style.width = '50%';
230
303
  const reactions = ui.divH([reactionsGridDiv, reactionExamples]);
@@ -260,11 +333,39 @@ export class RulesManager {
260
333
  const addButton = ui.button('Add rule', () => {
261
334
  const currentTab = inputsTabControl.currentPane.name;
262
335
  if (currentTab == TAB_LINKS)
263
- this.linkRuleDataFrame.rows.addNew();
336
+ this.getAddNewLinkRuleDialog();
264
337
  else if (currentTab == TAB_REACTIONS)
265
- this.synthRuleDataFrame.rows.addNew();
338
+ getReactionEditor((props: ReactionEditorProps) => this.addSynthRulesFunc(props));
266
339
  });
267
- const topPanel = [saveButton, addButton];
340
+ const removeButton = ui.button('Remove rule', () => {
341
+ const currentTab = inputsTabControl.currentPane.name;
342
+ if (currentTab == TAB_LINKS) {
343
+ if (this.linkRuleDataFrame == null || this.linkRuleDataFrame.currentRowIdx == -1 || this.linkRuleDataFrame.currentRowIdx == undefined)
344
+ return;
345
+ const idx = linksGrid.dataFrame.currentRowIdx;
346
+ ui.dialog('Are you sure you want to remove the rule?')
347
+ .add(ui.divText('This action is irreversible!'))
348
+ .onOK(() => {
349
+ this.linkRuleDataFrame.rows.removeAt(idx);
350
+ this.rules.setLinkRules(this.linkRuleDataFrame);
351
+ this.save();
352
+ }).show();
353
+ } else if (currentTab == TAB_REACTIONS) {
354
+ if (this.synthRuleDataFrame == null || this.synthRuleDataFrame.currentRowIdx == -1 || this.synthRuleDataFrame.currentRowIdx == undefined)
355
+ return;
356
+ const idx = synthesisGrid.dataFrame.currentRowIdx;
357
+ ui.dialog('Are you sure you want to remove the rule?')
358
+ .add(ui.divText('This action is irreversible!'))
359
+ .onOK(() => {
360
+ this.synthRuleDataFrame.rows.removeAt(idx);
361
+ this.rules.setSynthesisRules(this.synthRuleDataFrame);
362
+ this.substituteReactionGridDataFrame?.();
363
+ this.save();
364
+ }).show();
365
+ }
366
+ });
367
+
368
+ const topPanel = [saveButton, addButton, removeButton];
268
369
  this.v!.setRibbonPanels([topPanel]);
269
370
 
270
371
  panel.style.height = '100%';
@@ -272,5 +373,39 @@ export class RulesManager {
272
373
 
273
374
  return inputsTabControl.root;
274
375
  }
376
+
377
+ getAddNewLinkRuleDialog(preset?: Partial<LinkRuleRowArgs>): void {
378
+ const codeInput = ui.input.int('Code', {nullable: false, value: preset?.code});
379
+ const firstMonomersInput = ui.input.string('First monomers', {placeholder: 'E.g. C,D,E', value: preset?.firstMonomers,
380
+ tooltipText: 'Comma separated list of first monomers applicable for the rule. If left empty, all monomers will be considered', nullable: true});
381
+ const secondMonomersInput = ui.input.string('Second monomers', {placeholder: 'E.g. C,D,E', value: preset?.secondMonomers,
382
+ tooltipText: 'Comma separated list of second monomers applicable for the rule. If left empty, all monomers will be considered', nullable: true});
383
+ const firstLinkingGroup = preset?.firstLinkingGroup ? `R${preset.firstLinkingGroup}` : 'R3';
384
+ const secondLinkingGroup = preset?.secondLinkingGroup ? `R${preset.secondLinkingGroup}` : 'R3';
385
+ const firstLinkingGroupInput = ui.input.choice('First linking group', {value: firstLinkingGroup, items: ['R1', 'R2', 'R3', 'R4'],
386
+ tooltipText: 'Specifies which R-group of the first monomer will be used for linking', nullable: false});
387
+ const secondLinkingGroupInput = ui.input.choice('Second linking group', {value: secondLinkingGroup, items: ['R1', 'R2', 'R3', 'R4'],
388
+ tooltipText: 'Specifies which R-group of the second monomer will be used for linking', nullable: false});
389
+ ui.dialog('Add new link rule')
390
+ .add(codeInput)
391
+ .add(firstMonomersInput)
392
+ .add(secondMonomersInput)
393
+ .add(firstLinkingGroupInput)
394
+ .add(secondLinkingGroupInput)
395
+ .onOK(async () => {
396
+ // we rely on validation of inputs by DG inputs
397
+ const code = codeInput.value!;
398
+ const firstMonomers = (firstMonomersInput.value ?? '').split(',').map((s) => s.trim()).filter((s) => s).join(',');
399
+ const secondMonomers = (secondMonomersInput.value ?? '').split(',').map((s) => s.trim()).filter((s) => s).join(',');
400
+ const firstLinkingGroup = parseInt(firstLinkingGroupInput.value!.substring(1));
401
+ const secondLinkingGroup = parseInt(secondLinkingGroupInput.value!.substring(1));
402
+ this.addLinkRulesFunc({code, firstMonomers: firstMonomers ?? '', secondMonomers: secondMonomers ?? '',
403
+ firstLinkingGroup, secondLinkingGroup, row: preset?.row});
404
+
405
+ this.rules.setLinkRules(this.linkRuleDataFrame);
406
+ this.linkCards = await this.rules.getLinkCards();
407
+ this.save();
408
+ }).show();
409
+ }
275
410
  }
276
411
 
@@ -0,0 +1,125 @@
1
+ /* eslint-disable max-len */
2
+ import * as DG from 'datagrok-api/dg';
3
+ import * as grok from 'datagrok-api/grok';
4
+ import * as ui from 'datagrok-api/ui';
5
+
6
+ function correctRGroups(smiles: string): string {
7
+ const elementRGroupRegex = /\[R[1-9]\]/g;
8
+ // replace all [R1] with [*:1]
9
+ let correctedSmiles = smiles.replaceAll(elementRGroupRegex, (match) => {
10
+ const rGroupNum = match[2];
11
+ return `[*:${rGroupNum}]`;
12
+ });
13
+
14
+ // in some scenarios, rgroups can be written as [2*]
15
+ const elementRGroupRegex2 = /\[\d\*\]/g;
16
+ correctedSmiles = correctedSmiles.replaceAll(elementRGroupRegex2, (match) => {
17
+ const rGroupNum = match[1];
18
+ return `[*:${rGroupNum}]`;
19
+ });
20
+
21
+ // in some other scenarios, rgroups can be written as [1*:1] or [1*:0]
22
+ const elementRGroupRegex3 = /\[\d\*\:\d\]/g;
23
+ return correctedSmiles.replaceAll(elementRGroupRegex3, (match) => {
24
+ const rGroupNum = match[1];
25
+ return `[*:${rGroupNum}]`;
26
+ });
27
+ }
28
+
29
+ function rgroupToRadicalForm(smiles: string): string {
30
+ // here we are sure that all rgroups are in the form [*:1], [*:2], ..., [*:9]
31
+ // replace all [*:1] with [C:1] to mark carbons
32
+ return smiles.replaceAll(/\[\*\:\d\]/g, (match) => {
33
+ const rGroupNum = match[3];
34
+ return `[C:${rGroupNum}]`;
35
+ });
36
+ }
37
+
38
+ function radicalToRgroupForm(smiles: string): string {
39
+ // here we are sure that all marked carbons are in the form [C:1], [C:2], ..., [C:9]
40
+ // replace all [C:1] with [*:1]
41
+ return smiles.replaceAll(/\[C\:\d\]/g, (match) => {
42
+ const rGroupNum = match[3];
43
+ return `[*:${rGroupNum}]`;
44
+ });
45
+ }
46
+
47
+ export type ReactionEditorProps = {
48
+ resultMonomerName: string,
49
+ firstReactantSmiles: string,
50
+ secondReactantSmiles: string,
51
+ productSmiles: string,
52
+ firstMonomers: string[],
53
+ secondMonomers: string[],
54
+ code: number,
55
+ rowIndex?: number,
56
+ }
57
+
58
+ export function getReactionEditor(onOk: (props: ReactionEditorProps) => void, preset?: ReactionEditorProps) {
59
+ // preset smiles are in radical form, need to convert to rgroup form
60
+ const firstReactant = preset?.firstReactantSmiles ? radicalToRgroupForm(preset.firstReactantSmiles) : '[*:1]C';
61
+ const secondReactant = preset?.secondReactantSmiles ? radicalToRgroupForm(preset.secondReactantSmiles) : '[*:2]C';
62
+ const product = preset?.productSmiles ? radicalToRgroupForm(preset.productSmiles) : '[*:1]CC[*:2]';
63
+ const code = preset?.code;
64
+ const firstMonomers = preset?.firstMonomers ?? [];
65
+ const secondMonomers = preset?.secondMonomers ?? [];
66
+ const resultMonomerName = preset?.resultMonomerName ?? '';
67
+
68
+ const codeInput = ui.input.int('Code', {value: code, nullable: false, showPlusMinus: false, showSlider: false, tooltipText: 'Reaction code'});
69
+ const firstMonomersInput = ui.input.string('First monomers', {value: firstMonomers.join(','), nullable: false, tooltipText: 'Comma-separated list of monomers for the first reactant'});
70
+ const secondMonomersInput = ui.input.string('Second monomers', {value: secondMonomers.join(','), nullable: false, tooltipText: 'Comma-separated list of monomers for the second reactant'});
71
+ const resultMonomerNameInput = ui.input.string('Result monomer name', {value: resultMonomerName, nullable: false, tooltipText: 'Name of the resulting pseudo-monomer generated by the reaction of the two reactants'});
72
+
73
+ const firstReactantInput = ui.input.molecule('First reactant', {value: firstReactant, nullable: false, tooltipText: 'Reactant fragment of first monomer(s). Use numbered R groups in correspondence with the rest of the molecule.'});
74
+ const secondReactantInput = ui.input.molecule('Second reactant', {value: secondReactant, nullable: false, tooltipText: 'Reactant fragment of second monomer(s). Use numbered R groups in correspondence with the rest of the molecule.'});
75
+ const productInput = ui.input.molecule('Product', {value: product, nullable: false, tooltipText: 'Product fragment of the resulting monomer from the reaction of the two reactants. Use numbered R groups in correspondence with the reactants.'});
76
+
77
+ const validate = () => {
78
+ if (!firstReactantInput || !secondReactantInput || !productInput)
79
+ return 'Inputs not initialized yet';
80
+ const firstSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(firstReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
81
+ const secondSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(secondReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
82
+ const productSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(productInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
83
+ if (!firstSmiles || !secondSmiles || !productSmiles)
84
+ return 'First/Second Reactants and Product must all be set';
85
+ if (firstSmiles.indexOf('[C:1]') == -1)
86
+ return 'First Reactant must contain R1-group';
87
+ if (secondSmiles.indexOf('[C:2]') == -1)
88
+ return 'Second Reactant must contain R2-group';
89
+ if (productSmiles.indexOf('[C:1]') == -1 || productSmiles.indexOf('[C:2]') == -1)
90
+ return 'Product must contain R1- and R2-groups';
91
+ return null;
92
+ };
93
+ firstReactantInput.addValidator(validate);
94
+ secondReactantInput.addValidator(validate);
95
+ productInput.addValidator(validate);
96
+ const dialog = ui.dialog(preset ? 'Edit Reaction Rule' : 'Add Reaction Rule')
97
+ .add(codeInput)
98
+ .add(resultMonomerNameInput)
99
+ .add(firstMonomersInput)
100
+ .add(secondMonomersInput)
101
+ .add(firstReactantInput)
102
+ .add(secondReactantInput)
103
+ .add(productInput);
104
+ dialog.addButton('Save', () => {
105
+ const validationError = validate();
106
+ if (validationError != null) {
107
+ grok.shell.warning(validationError);
108
+ return;
109
+ }
110
+ const firstSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(firstReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
111
+ const secondSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(secondReactantInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
112
+ const productSmiles = rgroupToRadicalForm(correctRGroups(grok.chem.convert(productInput.value ?? '', grok.chem.Notation.Unknown, grok.chem.Notation.Smiles)));
113
+ onOk({
114
+ code: codeInput.value!,
115
+ resultMonomerName: resultMonomerNameInput.value,
116
+ firstMonomers: firstMonomersInput.value.split(',').map((s) => s.trim()).filter((s) => s.length > 0),
117
+ secondMonomers: secondMonomersInput.value.split(',').map((s) => s.trim()).filter((s) => s.length > 0),
118
+ firstReactantSmiles: firstSmiles,
119
+ secondReactantSmiles: secondSmiles,
120
+ productSmiles: productSmiles,
121
+ rowIndex: preset?.rowIndex});
122
+ dialog.close();
123
+ });
124
+ dialog.show();
125
+ }
@@ -7,9 +7,9 @@
7
7
  align-items: center;
8
8
  cursor: pointer;
9
9
  width: 200px;
10
- height: 210px;
10
+ height: 240px;
11
11
  min-width: 200px;
12
- min-height: 210px;
12
+ min-height: 240px;
13
13
  }
14
14
 
15
15
  .monomer-card-info-rules {
@@ -76,14 +76,14 @@ category('PolyTool: Convert', () => {
76
76
  'cyclized-D(2)-NH2(2)-3-0': {
77
77
  src: {seq: 'R-F-D(2)-T-G-H-F-Y-P-NH2(2)'},
78
78
  tgt: {
79
- helm: 'PEPTIDE1{R.F.D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,10:R2-3:R3$$$V2.0',
79
+ helm: 'PEPTIDE1{R.F.D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,3:R3-10:R2$$$V2.0',
80
80
  mol: {atomCount: 81, bondCount: 86, inchiKey: 'CBMGNYKOZWNVNK-AHGCAHLCSA-N'},
81
81
  }
82
82
  },
83
83
  'cyclized-D(2)-NH2(2)-0-0': {
84
84
  src: {seq: 'D(2)-T-G-H-F-Y-P-NH2(2)'},
85
85
  tgt: {
86
- helm: 'PEPTIDE1{D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,8:R2-1:R3$$$V2.0',
86
+ helm: 'PEPTIDE1{D.T.G.H.F.Y.P.[NH2]}$PEPTIDE1,PEPTIDE1,1:R3-8:R2$$$V2.0',
87
87
  mol: {atomCount: 59, bondCount: 63, inchiKey: 'HGRHAUQBJXFERJ-MUFWPYSASA-N'},
88
88
  }
89
89
  },