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