@datagrok/eda 1.4.11 → 1.4.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.
@@ -0,0 +1,580 @@
1
+ // Utility functions for probabilistic scoring (pMPO)
2
+ // Link: https://pmc.ncbi.nlm.nih.gov/articles/PMC4716604/
3
+
4
+ import * as grok from 'datagrok-api/grok';
5
+ import * as ui from 'datagrok-api/ui';
6
+ import * as DG from 'datagrok-api/dg';
7
+
8
+ import '../../css/pmpo.css';
9
+
10
+ import {COLORS, DESCR_TABLE_TITLE, DESCR_TITLE, DescriptorStatistics, DesirabilityProfileProperties,
11
+ DESIRABILITY_COL_NAME, FOLDER, P_VAL, PMPO_COMPUTE_FAILED, PmpoParams, SCORES_TITLE,
12
+ SELECTED_TITLE, STAT_TO_TITLE_MAP, TINY, WEIGHT_TITLE,
13
+ CorrelationTriple} from './pmpo-defs';
14
+ import {computeSigmoidParamsFromX0, getCutoffs, normalPdf, sigmoidS, solveNormalIntersection} from './stat-tools';
15
+ import {getColorScaleDiv} from '../pareto-optimization/utils';
16
+ import {OPT_TYPE} from '../pareto-optimization/defs';
17
+
18
+ /** Returns a DataFrame with descriptor statistics.
19
+ * @param stats Map of descriptor names to their statistics.
20
+ */
21
+ export function getDescriptorStatisticsTable(stats: Map<string, DescriptorStatistics>): DG.DataFrame {
22
+ const descrCount = stats.size;
23
+ const rawArrs = new Map<string, Float64Array>();
24
+
25
+ // Create raw data arrays
26
+ STAT_TO_TITLE_MAP.forEach((_, key) => {
27
+ rawArrs.set(key, new Float64Array(descrCount));
28
+ });
29
+
30
+ const descrNames = [...stats.keys()];
31
+ const cols = [
32
+ DG.Column.fromStrings(DESCR_TITLE, descrNames),
33
+ DG.Column.fromInt32Array(DESIRABILITY_COL_NAME, new Int32Array(descrCount)),
34
+ DG.Column.fromFloat32Array(WEIGHT_TITLE, new Float32Array(descrCount).fill(DG.FLOAT_NULL)),
35
+ ];
36
+
37
+ // Fill stat columns
38
+ descrNames.forEach((descr, idx) => {
39
+ const curStat = stats.get(descr);
40
+
41
+ if (curStat != null) {
42
+ STAT_TO_TITLE_MAP.forEach((_, key) => {
43
+ const val = curStat[key as keyof DescriptorStatistics];
44
+ const arr = rawArrs.get(key);
45
+ arr![idx] = val;
46
+ });
47
+ }
48
+ });
49
+
50
+ // Create stat columns
51
+ STAT_TO_TITLE_MAP.forEach((title, field) => {
52
+ cols.push(DG.Column.fromFloat64Array(title, rawArrs.get(field)!));
53
+ });
54
+
55
+ // Create the resulting table
56
+ const res = DG.DataFrame.fromColumns(cols);
57
+ res.name = DESCR_TABLE_TITLE;
58
+
59
+ return res;
60
+ } // getDescriptorStatisticsTable
61
+
62
+ /** Returns names of descriptors with p-value below the given threshold.
63
+ * @param descrStats DataFrame with descriptor statistics.
64
+ * @param pValThresh P-value threshold.
65
+ */
66
+ export function getFilteredByPvalue(descrStats: DG.DataFrame, pValThresh: number): string[] {
67
+ const selected: string[] = [];
68
+
69
+ const descrCol = descrStats.col(DESCR_TITLE);
70
+
71
+ if (descrCol == null)
72
+ throw new Error(`No column "${DESCR_TITLE} in the table with descriptors statistics.`);
73
+
74
+ const descr = descrCol.toList();
75
+
76
+ const pValCol = descrStats.col(P_VAL);
77
+
78
+ if (pValCol == null)
79
+ throw new Error(`No column "${P_VAL} in the table with descriptors statistics.`);
80
+
81
+ const pVals = pValCol.getRawData();
82
+
83
+ for (let i = 0; i < descrStats.rowCount; ++i) {
84
+ if (pVals[i] < pValThresh)
85
+ selected.push(descr[i]);
86
+ }
87
+
88
+ return selected;
89
+ } // getFilteredByPvalue
90
+
91
+ /** Adds a boolean column indicating whether each descriptor is selected.
92
+ * @param descrStats DataFrame with descriptor statistics.
93
+ * @param selected List of selected descriptor names.
94
+ */
95
+ export function addSelectedDescriptorsCol(descrStats: DG.DataFrame, selected: string[]): DG.DataFrame {
96
+ if (selected.length < 1)
97
+ throw new Error('Empty list of selected descriptors.');
98
+
99
+ const rowCount = descrStats.rowCount;
100
+ const selArr = new Array<boolean>(rowCount);
101
+ const descrCol = descrStats.col(DESCR_TITLE);
102
+
103
+ if (descrCol == null)
104
+ throw new Error(`No column "${DESCR_TITLE} in the table with descriptors statistics.`);
105
+
106
+ const descr = descrCol.toList();
107
+ let res = true;
108
+ const colors: Record<string, string> = {};
109
+
110
+ for (let i = 0; i < rowCount; ++i) {
111
+ res = selected.includes(descr[i]);
112
+ selArr[i] = res;
113
+ colors[descr[i]] = res ? COLORS.SELECTED : COLORS.SKIPPED;
114
+ }
115
+
116
+ descrCol.colors.setCategorical(colors);
117
+
118
+ // Added selected column
119
+ descrStats.columns.add(DG.Column.fromList(DG.COLUMN_TYPE.BOOL, SELECTED_TITLE, selArr));
120
+
121
+ return descrStats;
122
+ } // addSelectedDescriptorsCol
123
+
124
+ /** Returns tooltip element describing descriptor selection colors. */
125
+ export function getDescrTooltip(title: string, text: string, selected: string, excluded: string): HTMLElement {
126
+ const firstLine = ui.div();
127
+ firstLine.classList.add('eda-pmpo-tooltip-line');
128
+ const selectedBox = ui.div();
129
+ selectedBox.classList.add('eda-pmpo-box');
130
+ selectedBox.style.backgroundColor = COLORS.SELECTED;
131
+ const selectedLabel = ui.span([]);
132
+ selectedLabel.textContent = `- ${selected}`;
133
+ firstLine.appendChild(selectedBox);
134
+ firstLine.appendChild(selectedLabel);
135
+
136
+ const secondLine = ui.div();
137
+ secondLine.classList.add('eda-pmpo-tooltip-line');
138
+ const nonSelectedBox = ui.div();
139
+ nonSelectedBox.classList.add('eda-pmpo-box');
140
+ nonSelectedBox.style.backgroundColor = COLORS.SKIPPED;
141
+ const nonSelectedLabel = ui.span([]);
142
+ nonSelectedLabel.textContent = `- ${excluded}`;
143
+
144
+ secondLine.appendChild(nonSelectedBox);
145
+ secondLine.appendChild(nonSelectedLabel);
146
+
147
+ return ui.divV([ui.h2(title), ui.divText(text), firstLine, secondLine]);
148
+ } // getDescrTooltip
149
+
150
+ /** Returns tooltip element describing score colors. */
151
+ export function getScoreTooltip(): HTMLElement {
152
+ return ui.divV([
153
+ ui.h2(SCORES_TITLE),
154
+ ui.divText('Scores computed using the trained probabilistic multi-parameter optimization (pMPO) model.'),
155
+ getColorScaleDiv(OPT_TYPE.MAX, false),
156
+ ]);
157
+ } // getScoreTooltip
158
+
159
+ /** Returns list of descriptor correlation triples.
160
+ * @param descriptors Descriptor column list.
161
+ * @param selectedByPvalue List of descriptor names selected by p-value.
162
+ */
163
+ export function getCorrelationTriples(descriptors: DG.ColumnList, selectedByPvalue: string[]): CorrelationTriple[] {
164
+ const triples: CorrelationTriple[] = [];
165
+
166
+ for (let i = 0; i < selectedByPvalue.length; ++i) {
167
+ for (let j = i + 1; j < selectedByPvalue.length; ++j) {
168
+ triples.push([
169
+ selectedByPvalue[i],
170
+ selectedByPvalue[j],
171
+ descriptors.byName(selectedByPvalue[i]).stats.corr(descriptors.byName(selectedByPvalue[j]))**2,
172
+ ]);
173
+ }
174
+ }
175
+
176
+ return triples;
177
+ } // getCorrelationTriples
178
+
179
+ /** Returns names of descriptors filtered by correlation threshold.
180
+ * @param descriptors Descriptor column list.
181
+ * @param selectedByPvalue List of descriptor names selected by p-value.
182
+ */
183
+ export function getFilteredByCorrelations(descriptors: DG.ColumnList, selectedByPvalue: string[],
184
+ statistics: Map<string, DescriptorStatistics>, r2Tresh: number, correlationTriples: CorrelationTriple[]): string[] {
185
+ const correlations = correlationTriples.sort((a, b) => b[2] - a[2]);
186
+
187
+ const keep = new Set(selectedByPvalue);
188
+
189
+ correlations.filter((triple) => triple[2] > r2Tresh).forEach((triple) => {
190
+ const [descr1, descr2, _] = triple;
191
+ const pVal1 = statistics.get(descr1)!.pValue;
192
+ const pVal2 = statistics.get(descr2)!.pValue;
193
+ const tStat1 = statistics.get(descr1)!.tstat;
194
+ const tStat2 = statistics.get(descr2)!.tstat;
195
+
196
+ if (pVal1 > pVal2)
197
+ keep.delete(descr1);
198
+ else if (pVal1 < pVal2)
199
+ keep.delete(descr2);
200
+ else { // process the case of p-value = 0 (or too small)
201
+ if (Math.abs(tStat1) > Math.abs(tStat2))
202
+ keep.delete(descr2);
203
+ else
204
+ keep.delete(descr1);
205
+ }
206
+ });
207
+
208
+ return [...keep];
209
+ } // getFilteredByCorrelations
210
+
211
+ /** Computes pMPO model parameters for selected descriptors.
212
+ * @param desired DataFrame with desired compounds.
213
+ * @param nonDesired DataFrame with non-desired compounds.
214
+ * @param selected List of selected descriptor names.
215
+ * @param qCutoff Q-value cutoff.
216
+ */
217
+ export function getModelParams(desired: DG.DataFrame, nonDesired: DG.DataFrame,
218
+ selected: string[], qCutoff: number): Map<string, PmpoParams> {
219
+ const params = new Map<string, PmpoParams>();
220
+
221
+ let sum = 0;
222
+
223
+ // Compute params for each selected descriptor
224
+ selected.forEach((name) => {
225
+ const desLen = desired.rowCount;
226
+ const nonDesLen = nonDesired.rowCount;
227
+
228
+ const desCol = desired.col(name);
229
+ if (desCol == null)
230
+ throw new Error(PMPO_COMPUTE_FAILED + `: no column "${name}" in the desired table.`);
231
+
232
+ const nonDesCol = nonDesired.col(name);
233
+ if (nonDesCol == null)
234
+ throw new Error(PMPO_COMPUTE_FAILED + `: no column "${name}" in the non-desired table.`);
235
+
236
+ const muDes = desCol.stats.avg;
237
+
238
+ // Unbiased standard deviation
239
+ const sigmaDes = desCol.stats.stdev * Math.sqrt((desLen - 1) / desLen);
240
+
241
+ const muNonDes = nonDesCol.stats.avg;
242
+
243
+ // Unbiased standard deviation
244
+ const sigmaNonDes = nonDesCol.stats.stdev * Math.sqrt((nonDesLen - 1) / nonDesLen);
245
+
246
+ // Compute cutoffs and intersections
247
+ const cutoffs = getCutoffs(muDes, sigmaDes, muNonDes, sigmaNonDes);
248
+ const intersections = solveNormalIntersection(muDes, sigmaDes, muNonDes, sigmaNonDes);
249
+
250
+ // Compute parameters for the generalized sigmoid function
251
+
252
+ let x0: number | null = null;
253
+
254
+ if (intersections.length > 0) {
255
+ for (const r of intersections) {
256
+ const low = Math.min(muDes, muNonDes);
257
+ const high = Math.max(muDes, muNonDes);
258
+
259
+ if ((low - TINY <= r) && (r <= high + TINY)) {
260
+ x0 = r;
261
+ break;
262
+ }
263
+ }
264
+
265
+ if (x0 == null)
266
+ x0 = intersections[0];
267
+ } else
268
+ x0 = cutoffs.cutoff;
269
+
270
+ const xBound = cutoffs.cutoffNotDesired;
271
+ const sigmoidParams = computeSigmoidParamsFromX0(muDes, sigmaDes, x0, xBound, qCutoff);
272
+
273
+ const z = Math.abs(muDes - muNonDes) / (sigmaDes + sigmaNonDes);
274
+ sum += z;
275
+
276
+ // Store computed parameters
277
+ params.set(name, {
278
+ desAvg: muDes,
279
+ desStd: sigmaDes,
280
+ nonDesAvg: muNonDes,
281
+ nonDesStd: sigmaNonDes,
282
+ cutoff: cutoffs.cutoff,
283
+ cutoffDesired: cutoffs.cutoffDesired,
284
+ cutoffNotDesired: cutoffs.cutoffNotDesired,
285
+ pX0: sigmoidParams.pX0,
286
+ b: sigmoidParams.b,
287
+ c: sigmoidParams.c,
288
+ zScore: z,
289
+ weight: z,
290
+ intersections: intersections,
291
+ x0: x0,
292
+ xBound: xBound,
293
+ });
294
+ });
295
+
296
+ // Normalize weights
297
+ params.forEach((param) => {
298
+ param.weight = param.zScore / sum;
299
+ });
300
+
301
+ return params;
302
+ } // getModelParams
303
+
304
+ /** Returns a DataFrame with descriptor weights.
305
+ * @param params Map of descriptor names to their pMPO parameters.
306
+ */
307
+ export function getWeightsTable(params: Map<string, PmpoParams>): DG.DataFrame {
308
+ const count = params.size;
309
+ const descriptors = new Array<string>(count);
310
+ const weights = new Float64Array(count);
311
+
312
+ let idx = 0;
313
+
314
+ params.forEach((param, name) => {
315
+ descriptors[idx] = name;
316
+ weights[idx] = param.weight;
317
+ ++idx;
318
+ });
319
+
320
+ return DG.DataFrame.fromColumns([
321
+ DG.Column.fromStrings(DESCR_TITLE, descriptors),
322
+ DG.Column.fromFloat64Array(WEIGHT_TITLE, weights),
323
+ ]);
324
+ } // getWeightsTable
325
+
326
+ /** Loads pMPO model parameters from a file.
327
+ * @param file FileInfo object pointing to the JSON model file.
328
+ */
329
+ export async function loadPmpoParams(file: DG.FileInfo): Promise<Map<string, PmpoParams>> {
330
+ const jsonText = await file.readAsString();
331
+ const parsedObj = JSON.parse(jsonText);
332
+
333
+ return new Map(Object.entries(parsedObj.properties));
334
+ } // loadPmpoParams
335
+
336
+ /** Returns JSON object representing an MPO Desirability Profile.
337
+ * @param params Map of descriptor names to their pMPO parameters.
338
+ * @param name Name of the desirability profile.
339
+ * @param description Description of the desirability profile.
340
+ */
341
+ export function getDesirabilityProfileJson(params: Map<string, PmpoParams>, name: string, description: string) {
342
+ return {
343
+ 'type': 'MPO Desirability Profile',
344
+ 'name': name,
345
+ 'description': description,
346
+ 'properties': getDesirabilityProfileProperties(params),
347
+ };
348
+ }
349
+
350
+ /** Saves pMPO model parameters to a file.
351
+ * @param params Map of descriptor names to their pMPO parameters.
352
+ * @param modelName Suggested model name (used as default file name).
353
+ */
354
+ export async function saveModel(params: Map<string, PmpoParams>, modelName: string): Promise<void> {
355
+ let fileName = modelName;
356
+ const nameInput = ui.input.string('File', {
357
+ value: fileName,
358
+ nullable: false,
359
+ onValueChanged: (val) => {
360
+ fileName = val;
361
+ dlg.getButton('Save').disabled = (fileName.length < 1) || (folderName.length < 1);
362
+ },
363
+ });
364
+
365
+ let folderName = FOLDER;
366
+ const folderInput = ui.input.string('Folder', {
367
+ value: folderName,
368
+ nullable: false,
369
+ onValueChanged: (val) => {
370
+ folderName = val;
371
+ dlg.getButton('Save').disabled = (fileName.length < 1) || (folderName.length < 1);
372
+ },
373
+ });
374
+
375
+ const save = async () => {
376
+ const path = `${folderName}/${fileName}.json`;
377
+ try {
378
+ const jsonString = JSON.stringify(objectToSave(), null, 2);
379
+ await grok.dapi.files.writeAsText(path, jsonString);
380
+ grok.shell.info(`Saved to ${path}`);
381
+ } catch (err) {
382
+ grok.shell.error(`Failed to save: ${err instanceof Error ? err.message : 'the platform issue'}.`);
383
+ }
384
+ dlg.close();
385
+ };
386
+
387
+ const objectToSave = () => {
388
+ if (typeInput.value) {
389
+ return getDesirabilityProfileJson(
390
+ params,
391
+ nameInput.value,
392
+ descriptionInput.value,
393
+ );
394
+ }
395
+
396
+ return {
397
+ 'type': 'Probabilistic MPO Model',
398
+ 'name': nameInput.value,
399
+ 'description': descriptionInput.value,
400
+ 'properties': Object.fromEntries(params),
401
+ };
402
+ };
403
+
404
+ const modelNameInput = ui.input.string('Name', {value: modelName, nullable: true});
405
+ const descriptionInput = ui.input.textArea('Description', {value: ' ', nullable: true});
406
+ const typeInput = ui.input.bool('Desirability Profile', {
407
+ value: true,
408
+ tooltipText: 'Save the model as an MPO Desirability Profile. If disabled, the model is saved in the pMPO format.',
409
+ });
410
+
411
+ const dlg = ui.dialog({title: 'Save model'})
412
+ .add(ui.h2('Path'))
413
+ .add(folderInput)
414
+ .add(nameInput)
415
+ .add(ui.h2('Model'))
416
+ .add(modelNameInput)
417
+ .add(descriptionInput)
418
+ .add(typeInput)
419
+ .addButton('Save', async () => {
420
+ const exist = await grok.dapi.files.exists(`${folderName}/${fileName}.json`);
421
+ if (!exist)
422
+ await save();
423
+ else {
424
+ // Handle overwrite confirmation
425
+ ui.dialog({title: 'Warning'})
426
+ .add(ui.label('Overwrite existing file?'))
427
+ .onOK(async () => await save())
428
+ .show();
429
+ }
430
+ })
431
+ .show();
432
+ } // saveModel
433
+
434
+ /** Adds columns with correlation coefficients between descriptors.
435
+ * @param df DataFrame to which the columns will be added.
436
+ * @param descriptorNames List of descriptor names.
437
+ * @param triples List of descriptor correlation triples.
438
+ * @param selectedByCorr List of descriptor names selected after correlation filtering.
439
+ */
440
+ export function addCorrelationColumns(df: DG.DataFrame, descriptorNames: string[],
441
+ triples: CorrelationTriple[], selectedByCorr: string[]): DG.DataFrame {
442
+ const raw = new Map<string, Float32Array>();
443
+ descriptorNames.forEach((name) => {
444
+ raw.set(name, new Float32Array(descriptorNames.length).fill(DG.FLOAT_NULL));
445
+ });
446
+
447
+ const descrColVals = df.col(DESCR_TITLE)!.toList();
448
+
449
+ triples.forEach((triple) => {
450
+ const [descr1, descr2, r2] = triple;
451
+
452
+ raw.get(descr1)![descrColVals.indexOf(descr2)] = r2;
453
+ raw.get(descr2)![descrColVals.indexOf(descr1)] = r2;
454
+ raw.get(descr1)![descrColVals.indexOf(descr1)] = 1;
455
+ raw.get(descr2)![descrColVals.indexOf(descr2)] = 1;
456
+ });
457
+
458
+ selectedByCorr.forEach((name) => {
459
+ df.columns.add(DG.Column.fromFloat32Array(name, raw.get(name)!));
460
+ });
461
+
462
+ return df;
463
+ } // addCorrelationColumns
464
+
465
+ /* Sets color coding for the p-value column in the statistics table */
466
+ export function setPvalColumnColorCoding(table: DG.DataFrame, pValTresh: number): void {
467
+ const pValCol = table.col(P_VAL);
468
+ if (pValCol == null)
469
+ return;
470
+
471
+ const rules: Record<string, string> = {};
472
+ rules[`<${pValTresh}`] = COLORS.SELECTED;
473
+ rules[`>=${pValTresh}`] = COLORS.SKIPPED;
474
+
475
+ pValCol.meta.colors.setConditional(rules);
476
+ } // setPvalColumnColorCoding
477
+
478
+ /* Sets color coding for the p-value column in the statistics table */
479
+ export function setCorrColumnColorCoding(table: DG.DataFrame, descriptorNames: string[], r2Tresh: number): void {
480
+ descriptorNames.forEach((name) => {
481
+ const col = table.col(name);
482
+ if (col == null)
483
+ return;
484
+
485
+ const rules: Record<string, string> = {};
486
+ rules[`>${r2Tresh}`] = COLORS.SKIPPED;
487
+ rules[`=<${r2Tresh}`] = COLORS.SELECTED;
488
+
489
+ col.meta.colors.setConditional(rules);
490
+ });
491
+ } // setCorrColumnColorCoding
492
+
493
+ /** Returns desirability profile properties for the given pMPO parameters.
494
+ * @param params Map of descriptor names to their pMPO parameters.
495
+ */
496
+ function getDesirabilityProfileProperties(params: Map<string, PmpoParams>) {
497
+ const props: DesirabilityProfileProperties = {};
498
+
499
+ let maxWeight = 0;
500
+ params.forEach((param) => maxWeight = Math.max(maxWeight, param.weight));
501
+
502
+ const scale = (maxWeight > 0) ? (1 / maxWeight) : 1;
503
+
504
+ params.forEach((param, name) => {
505
+ const range = significantPoints(param);
506
+ props[name] = {
507
+ weight: param.weight * scale,
508
+ line: getLine(param),
509
+ min: Math.min(...range),
510
+ max: Math.max(...range),
511
+ };
512
+ });
513
+
514
+ return props;
515
+ } // getDesirabilityProfileProperties
516
+
517
+ /** Returns array of arguments for Gaussian function centered at mu with stddev sigma. */
518
+ function getArgsOfGaussFunc(mu: number, sigma: number): number[] {
519
+ return [
520
+ mu - 3 * sigma,
521
+ mu - 2.5 * sigma,
522
+ mu - 2 * sigma,
523
+ mu - 1.5 * sigma,
524
+ mu - sigma,
525
+ mu - 0.5 * sigma,
526
+ mu - 0.25 * sigma,
527
+ mu,
528
+ mu + 0.25 * sigma,
529
+ mu + 0.5 * sigma,
530
+ mu + sigma,
531
+ mu + 1.5 * sigma,
532
+ mu + 2 * sigma,
533
+ mu + 2.5 * sigma,
534
+ mu + 3 * sigma,
535
+ ];
536
+ } // getArgsOfGaussFunc
537
+
538
+ /** Returns scale factor for the given pMPO parameters and range of x values. */
539
+ function getScale(param: PmpoParams, range: number[]): number {
540
+ const values = range.map((x) => basicFunction(x, param));
541
+
542
+ return Math.max(...values);
543
+ }
544
+
545
+ /** Basic pMPO function combining Gaussian and sigmoid functions. */
546
+ function basicFunction(x: number, param: PmpoParams): number {
547
+ return normalPdf(x, param.desAvg, param.desStd) * sigmoidS(x, param.x0, param.b, param.c);
548
+ }
549
+
550
+ /** Returns line points for the given pMPO parameters. */
551
+ function getLine(param: PmpoParams): [number, number][] {
552
+ //const range = getArgsOfGaussFunc(param.desAvg, param.desStd);
553
+ const range = significantPoints(param);
554
+ const scale = getScale(param, range);
555
+
556
+ return range.map((x) => [x, basicFunction(x, param) / scale]);
557
+ }
558
+
559
+ /** Returns significant points for the given pMPO parameters. */
560
+ function significantPoints(param: PmpoParams): number[] {
561
+ const start = param.desAvg - 10 * param.desStd;
562
+ const end = param.desAvg + 10 * param.desStd;
563
+ const steps = 1000;
564
+
565
+ let arg = start;
566
+ let func = basicFunction(arg, param);
567
+ let x = 0;
568
+ let y = 0;
569
+
570
+ for (let i = 0; i <= steps; i++) {
571
+ x = start + ((end - start) * i) / steps;
572
+ y = basicFunction(x, param);
573
+ if (y > func) {
574
+ arg = x;
575
+ func = y;
576
+ }
577
+ }
578
+
579
+ return getArgsOfGaussFunc(arg, param.desStd);
580
+ } // significantPoints