@datagrok/eda 1.4.12 → 1.5.0
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 +10 -0
- package/CLAUDE.md +185 -0
- package/css/pmpo.css +9 -0
- package/dist/111.js +1 -1
- package/dist/111.js.map +1 -1
- package/dist/128.js +1 -1
- package/dist/128.js.map +1 -1
- package/dist/153.js +1 -1
- package/dist/153.js.map +1 -1
- package/dist/23.js +1 -1
- package/dist/23.js.map +1 -1
- package/dist/234.js +1 -1
- package/dist/234.js.map +1 -1
- package/dist/242.js +1 -1
- package/dist/242.js.map +1 -1
- package/dist/260.js +1 -1
- package/dist/260.js.map +1 -1
- package/dist/33.js +1 -1
- package/dist/33.js.map +1 -1
- package/dist/348.js +1 -1
- package/dist/348.js.map +1 -1
- package/dist/377.js +1 -1
- package/dist/377.js.map +1 -1
- package/dist/397.js +2 -0
- package/dist/397.js.map +1 -0
- package/dist/412.js +1 -1
- package/dist/412.js.map +1 -1
- package/dist/415.js +1 -1
- package/dist/415.js.map +1 -1
- package/dist/501.js +1 -1
- package/dist/501.js.map +1 -1
- package/dist/531.js +1 -1
- package/dist/531.js.map +1 -1
- package/dist/583.js +1 -1
- package/dist/583.js.map +1 -1
- package/dist/589.js +1 -1
- package/dist/589.js.map +1 -1
- package/dist/603.js +1 -1
- package/dist/603.js.map +1 -1
- package/dist/656.js +1 -1
- package/dist/656.js.map +1 -1
- package/dist/682.js +1 -1
- package/dist/682.js.map +1 -1
- package/dist/705.js +1 -1
- package/dist/705.js.map +1 -1
- package/dist/727.js +1 -1
- package/dist/727.js.map +1 -1
- package/dist/731.js +1 -1
- package/dist/731.js.map +1 -1
- package/dist/738.js +1 -1
- package/dist/738.js.map +1 -1
- package/dist/763.js +1 -1
- package/dist/763.js.map +1 -1
- package/dist/778.js +1 -1
- package/dist/778.js.map +1 -1
- package/dist/783.js +1 -1
- package/dist/783.js.map +1 -1
- package/dist/793.js +1 -1
- package/dist/793.js.map +1 -1
- package/dist/810.js +1 -1
- package/dist/810.js.map +1 -1
- package/dist/860.js +1 -1
- package/dist/860.js.map +1 -1
- package/dist/907.js +1 -1
- package/dist/907.js.map +1 -1
- package/dist/950.js +1 -1
- package/dist/950.js.map +1 -1
- package/dist/980.js +1 -1
- package/dist/980.js.map +1 -1
- package/dist/990.js +1 -1
- package/dist/990.js.map +1 -1
- 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 +0 -1
- package/files/drugs-props-train-scores.csv +664 -0
- package/package.json +11 -7
- package/src/package-api.ts +7 -3
- package/src/package-test.ts +4 -1
- package/src/package.g.ts +21 -9
- package/src/package.ts +33 -23
- package/src/pareto-optimization/pareto-computations.ts +6 -0
- package/src/pareto-optimization/pareto-optimizer.ts +1 -1
- package/src/pls/pls-constants.ts +3 -1
- package/src/pls/pls-tools.ts +73 -69
- package/src/probabilistic-scoring/data-generator.ts +202 -0
- package/src/probabilistic-scoring/nelder-mead.ts +204 -0
- package/src/probabilistic-scoring/pmpo-defs.ts +141 -3
- package/src/probabilistic-scoring/pmpo-utils.ts +240 -126
- package/src/probabilistic-scoring/prob-scoring.ts +862 -135
- package/src/probabilistic-scoring/stat-tools.ts +141 -6
- 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 +251 -0
- package/src/tests/pmpo-tests.ts +797 -0
- package/test-console-output-1.log +303 -239
- package/test-record-1.mp4 +0 -0
- package/files/mpo-done.ipynb +0 -2123
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Probabilistic scoring (pMPO) statistical tools
|
|
2
|
-
// Link
|
|
2
|
+
// Link https://pmc.ncbi.nlm.nih.gov/articles/PMC4716604/
|
|
3
3
|
|
|
4
4
|
import * as grok from 'datagrok-api/grok';
|
|
5
5
|
import * as ui from 'datagrok-api/ui';
|
|
@@ -8,9 +8,8 @@ import * as DG from 'datagrok-api/dg';
|
|
|
8
8
|
//@ts-ignore: no types
|
|
9
9
|
import * as jStat from 'jstat';
|
|
10
10
|
|
|
11
|
-
import {Cutoff, DescriptorStatistics,
|
|
12
|
-
|
|
13
|
-
const SQRT_2_PI = Math.sqrt(2 * Math.PI);
|
|
11
|
+
import {ConfusionMatrix, Cutoff, DescriptorStatistics, ModelEvaluationResult,
|
|
12
|
+
ROC_TRESHOLDS, ROC_TRESHOLDS_COUNT, SigmoidParams} from './pmpo-defs';
|
|
14
13
|
|
|
15
14
|
/** Splits the dataframe into desired and non-desired tables based on the desirability column */
|
|
16
15
|
export function getDesiredTables(df: DG.DataFrame, desirability: DG.Column) {
|
|
@@ -70,6 +69,8 @@ export function getDescriptorStatistics(des: DG.Column, nonDes: DG.Column): Desc
|
|
|
70
69
|
nonDesAvg: nonDesAvg,
|
|
71
70
|
nonDesStd: nonDesStd,
|
|
72
71
|
nonSesLen: nonDesLen,
|
|
72
|
+
min: Math.min(des.stats.min, nonDes.stats.min),
|
|
73
|
+
max: Math.max(des.stats.max, nonDes.stats.max),
|
|
73
74
|
tstat: t,
|
|
74
75
|
pValue: pValue,
|
|
75
76
|
};
|
|
@@ -163,6 +164,140 @@ export function sigmoidS(x: number, x0: number, b: number, c: number): number {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
/** Normal probability density function */
|
|
166
|
-
export function
|
|
167
|
-
return Math.exp(-((x - mu)**2) / (2 * sigma**2))
|
|
167
|
+
export function gaussDesirabilityFunc(x: number, mu: number, sigma: number): number {
|
|
168
|
+
return Math.exp(-((x - mu)**2) / (2 * sigma**2));
|
|
168
169
|
}
|
|
170
|
+
|
|
171
|
+
/** Computes the confusion matrix given desirability (labels) and prediction columns
|
|
172
|
+
* @param desirability - desirability column (boolean)
|
|
173
|
+
* @param prediction - prediction column (numeric)
|
|
174
|
+
* @param threshold - threshold to convert prediction scores to binary labels
|
|
175
|
+
* @return ConfusionMatrix object with TP, TN, FP, FN counts
|
|
176
|
+
*/
|
|
177
|
+
export function getConfusionMatrix(desirability: DG.Column, prediction: DG.Column, threshold: number): ConfusionMatrix {
|
|
178
|
+
if (desirability.length !== prediction.length)
|
|
179
|
+
throw new Error('Failed to compute confusion matrix: columns have different lengths.');
|
|
180
|
+
|
|
181
|
+
if (desirability.type !== DG.COLUMN_TYPE.BOOL)
|
|
182
|
+
throw new Error('Failed to compute confusion matrix: desirability column must be boolean.');
|
|
183
|
+
|
|
184
|
+
if (!prediction.isNumerical)
|
|
185
|
+
throw new Error('Failed to compute confusion matrix: prediction column must be numerical.');
|
|
186
|
+
|
|
187
|
+
let TP = 0;
|
|
188
|
+
let TN = 0;
|
|
189
|
+
let FP = 0;
|
|
190
|
+
let FN = 0;
|
|
191
|
+
|
|
192
|
+
const desRaw = desirability.getRawData();
|
|
193
|
+
const predRaw = prediction.getRawData();
|
|
194
|
+
|
|
195
|
+
let desIdx = 0;
|
|
196
|
+
let curPos = 0;
|
|
197
|
+
let desElem = desRaw[0];
|
|
198
|
+
|
|
199
|
+
// Here, we extract bits from the desirability boolean column in chunks of 32 bits
|
|
200
|
+
for (let predIdx = 0; predIdx < prediction.length; ++predIdx) {
|
|
201
|
+
// console.log(predIdx + 1, ': ',
|
|
202
|
+
// desirability.get(predIdx), '<-->', (desElem >>> curPos) & 1, ' vs ', predRaw[predIdx] >= threshold);
|
|
203
|
+
|
|
204
|
+
if (((desElem >>> curPos) & 1) == 1) { // True actual
|
|
205
|
+
if (predRaw[predIdx] >= threshold) { // True predicted
|
|
206
|
+
++TP;
|
|
207
|
+
} else { // False predicted
|
|
208
|
+
++FN;
|
|
209
|
+
}
|
|
210
|
+
} else { // False actual
|
|
211
|
+
if (predRaw[predIdx] >= threshold) { // True predicted
|
|
212
|
+
++FP;
|
|
213
|
+
} else { // False predicted
|
|
214
|
+
++TN;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
++curPos;
|
|
219
|
+
|
|
220
|
+
// Move to the next desirability element if we have processed 32 bits
|
|
221
|
+
if (curPos >= 32) {
|
|
222
|
+
curPos = 0;
|
|
223
|
+
++desIdx;
|
|
224
|
+
desElem = desRaw[desIdx];
|
|
225
|
+
}
|
|
226
|
+
} // for predIdx
|
|
227
|
+
|
|
228
|
+
return {TP: TP, TN: TN, FP: FP, FN: FN};
|
|
229
|
+
} // getConfusionMatrix
|
|
230
|
+
|
|
231
|
+
/** Computes Area Under Curve (AUC) given TPR and FPR arrays
|
|
232
|
+
* @param tpr - True Positive Rate array
|
|
233
|
+
* @param fpr - False Positive Rate array
|
|
234
|
+
* @return AUC value
|
|
235
|
+
*/
|
|
236
|
+
export function getAuc(tpr: Float32Array, fpr: Float32Array): number {
|
|
237
|
+
if (tpr.length !== fpr.length)
|
|
238
|
+
throw new Error('Failed to compute AUC: TPR and FPR arrays have different lengths.');
|
|
239
|
+
|
|
240
|
+
let auc = 0.0;
|
|
241
|
+
|
|
242
|
+
for (let i = 1; i < tpr.length; ++i) {
|
|
243
|
+
const xDiff = Math.abs(fpr[i] - fpr[i - 1]);
|
|
244
|
+
const yAvg = (tpr[i] + tpr[i - 1]) / 2.0;
|
|
245
|
+
auc += xDiff * yAvg;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return auc;
|
|
249
|
+
} // getAuc
|
|
250
|
+
|
|
251
|
+
/** Converts numeric prediction column to boolean based on the given threshold
|
|
252
|
+
* @param numericPrediction - numeric prediction column
|
|
253
|
+
* @param threshold - threshold to convert prediction scores to binary labels
|
|
254
|
+
* @param name - name for the resulting boolean column
|
|
255
|
+
* @return Boolean prediction column
|
|
256
|
+
*/
|
|
257
|
+
export function getBoolPredictionColumn(numericPrediction: DG.Column, threshold: number, name: string): DG.Column {
|
|
258
|
+
if (!numericPrediction.isNumerical)
|
|
259
|
+
throw new Error('Failed to compute confusion matrix: prediction column must be numerical.');
|
|
260
|
+
|
|
261
|
+
const size = numericPrediction.length;
|
|
262
|
+
const boolPredData = new Array<boolean>(size);
|
|
263
|
+
const predRaw = numericPrediction.getRawData();
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < size; ++i)
|
|
266
|
+
boolPredData[i] = (predRaw[i] >= threshold);
|
|
267
|
+
|
|
268
|
+
return DG.Column.fromList(DG.COLUMN_TYPE.BOOL, name, boolPredData);
|
|
269
|
+
} // getBoolPredictionColumn
|
|
270
|
+
|
|
271
|
+
/** Computes pMPO model evaluation metrics: AUC, optimal threshold, TPR and FPR arrays
|
|
272
|
+
* @param desirability - desirability column (boolean)
|
|
273
|
+
* @param prediction - prediction column (numeric)
|
|
274
|
+
* @return ModelEvaluationResult object with AUC, optimal threshold, TPR and FPR arrays
|
|
275
|
+
*/
|
|
276
|
+
export function getPmpoEvaluation(desirability: DG.Column, prediction: DG.Column): ModelEvaluationResult {
|
|
277
|
+
const tpr = new Float32Array(ROC_TRESHOLDS_COUNT);
|
|
278
|
+
const fpr = new Float32Array(ROC_TRESHOLDS_COUNT);
|
|
279
|
+
|
|
280
|
+
let bestJ = -1;
|
|
281
|
+
let currentJ = -1;
|
|
282
|
+
let bestThreshold = ROC_TRESHOLDS[0];
|
|
283
|
+
|
|
284
|
+
// Compute TPR and FPR for each threshold
|
|
285
|
+
for (let i = 0; i < ROC_TRESHOLDS_COUNT; ++i) {
|
|
286
|
+
const confusion = getConfusionMatrix(desirability, prediction, ROC_TRESHOLDS[i]);
|
|
287
|
+
tpr[i] = (confusion.TP + confusion.FN) > 0 ? confusion.TP / (confusion.TP + confusion.FN) : 0;
|
|
288
|
+
fpr[i] = (confusion.FP + confusion.TN) > 0 ? confusion.FP / (confusion.FP + confusion.TN) : 0;
|
|
289
|
+
currentJ = tpr[i] - fpr[i];
|
|
290
|
+
|
|
291
|
+
if (currentJ > bestJ) {
|
|
292
|
+
bestJ = currentJ;
|
|
293
|
+
bestThreshold = ROC_TRESHOLDS[i];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
auc: getAuc(tpr, fpr),
|
|
299
|
+
threshold: bestThreshold,
|
|
300
|
+
tpr: tpr,
|
|
301
|
+
fpr: fpr,
|
|
302
|
+
};
|
|
303
|
+
} // getPmpoEvaluation
|
package/src/tests/anova-tests.ts
CHANGED
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import * as DG from 'datagrok-api/dg';
|
|
6
6
|
import {_package} from '../package-test';
|
|
7
7
|
|
|
8
|
-
import {category, expect, test} from '@datagrok-libraries/
|
|
8
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
9
9
|
|
|
10
10
|
import {oneWayAnova, FactorizedData} from '../anova/anova-tools';
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import * as DG from 'datagrok-api/dg';
|
|
6
6
|
import {_package} from '../package-test';
|
|
7
7
|
|
|
8
|
-
import {category, expect, test} from '@datagrok-libraries/
|
|
8
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
9
9
|
|
|
10
10
|
import {classificationDataset, accuracy} from './utils';
|
|
11
11
|
import {SoftmaxClassifier} from '../softmax-classifier';
|
|
@@ -5,7 +5,7 @@ import {_package} from '../package-test';
|
|
|
5
5
|
|
|
6
6
|
// tests for dimensionality reduction
|
|
7
7
|
|
|
8
|
-
import {category, expect, test} from '@datagrok-libraries/
|
|
8
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
9
9
|
import {DimReductionMethods} from '@datagrok-libraries/ml/src/multi-column-dimensionality-reduction/types';
|
|
10
10
|
import {KnownMetrics, NumberMetricsNames, StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
|
|
11
11
|
import {multiColReduceDimensionality}
|
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import * as DG from 'datagrok-api/dg';
|
|
6
6
|
import {_package} from '../package-test';
|
|
7
7
|
|
|
8
|
-
import {category, expect, test} from '@datagrok-libraries/
|
|
8
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
9
9
|
import {computePCA} from '../eda-tools';
|
|
10
10
|
import {getPlsAnalysis} from '../pls/pls-tools';
|
|
11
11
|
import {PlsModel} from '../pls/pls-ml';
|
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import * as DG from 'datagrok-api/dg';
|
|
6
6
|
import {_package} from '../package-test';
|
|
7
7
|
|
|
8
|
-
import {category, expect, test} from '@datagrok-libraries/
|
|
8
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
9
9
|
|
|
10
10
|
import {MetricInfo, DISTANCE_TYPE, impute} from '../missing-values-imputation/knn-imputer';
|
|
11
11
|
import {getFeatureInputSettings} from '../missing-values-imputation/ui';
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Tests for Pareto Front Computations
|
|
2
|
+
// Performance tests for the Pareto optimality algorithm
|
|
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
|
+
import {_package} from '../package-test';
|
|
8
|
+
|
|
9
|
+
import {category, expect, test} from '@datagrok-libraries/test/src/test';
|
|
10
|
+
|
|
11
|
+
import {getParetoMask} from '../pareto-optimization/pareto-computations';
|
|
12
|
+
import {OPT_TYPE, NumericArray} from '../pareto-optimization/defs';
|
|
13
|
+
|
|
14
|
+
const TIMEOUT = 5000;
|
|
15
|
+
|
|
16
|
+
// Test dataset sizes
|
|
17
|
+
const ROWS_COUNT = 1000000;
|
|
18
|
+
const M = 1000000;
|
|
19
|
+
const COLS_COUNT = 2;
|
|
20
|
+
const suffix = M < 1e6 ? 'K' : 'M';
|
|
21
|
+
const DATASET_SIZE_LABEL = `${ROWS_COUNT / M}${suffix} points, ${COLS_COUNT}D`;
|
|
22
|
+
|
|
23
|
+
/** Generates synthetic numeric data for Pareto front testing */
|
|
24
|
+
function generateSyntheticData(nPoints: number, nDims: number, seed: number = 42): NumericArray[] {
|
|
25
|
+
const data: NumericArray[] = [];
|
|
26
|
+
|
|
27
|
+
// Simple deterministic pseudo-random generator for reproducibility
|
|
28
|
+
let rng = seed;
|
|
29
|
+
const random = () => {
|
|
30
|
+
rng = (rng * 1664525 + 1013904223) % 4294967296;
|
|
31
|
+
return rng / 4294967296;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (let d = 0; d < nDims; d++) {
|
|
35
|
+
const column = new Float32Array(nPoints);
|
|
36
|
+
for (let i = 0; i < nPoints; i++) {
|
|
37
|
+
// Generate values with some correlation to create realistic Pareto fronts
|
|
38
|
+
column[i] = random() * 100 + (d * 10);
|
|
39
|
+
}
|
|
40
|
+
data.push(column);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Generates optimization sense array */
|
|
47
|
+
function generateSense(nDims: number, pattern: 'all-min' | 'all-max' | 'mixed'): OPT_TYPE[] {
|
|
48
|
+
const sense: OPT_TYPE[] = [];
|
|
49
|
+
|
|
50
|
+
for (let d = 0; d < nDims; d++) {
|
|
51
|
+
if (pattern === 'all-min')
|
|
52
|
+
sense.push(OPT_TYPE.MIN);
|
|
53
|
+
else if (pattern === 'all-max')
|
|
54
|
+
sense.push(OPT_TYPE.MAX);
|
|
55
|
+
else {
|
|
56
|
+
// Mixed: alternate between MIN and MAX
|
|
57
|
+
sense.push(d % 2 === 0 ? OPT_TYPE.MIN : OPT_TYPE.MAX);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return sense;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Generates null indices set for testing missing value handling */
|
|
65
|
+
function generateNullIndices(nPoints: number, nullRatio: number): Set<number> {
|
|
66
|
+
const nullCount = Math.floor(nPoints * nullRatio);
|
|
67
|
+
const nullIndices = new Set<number>();
|
|
68
|
+
|
|
69
|
+
// Distribute null indices evenly
|
|
70
|
+
const step = Math.floor(nPoints / nullCount);
|
|
71
|
+
for (let i = 0; i < nullCount; i++)
|
|
72
|
+
nullIndices.add(i * step);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
return nullIndices;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Validates Pareto mask result */
|
|
79
|
+
function validateParetoMask(mask: boolean[], nPoints: number): void {
|
|
80
|
+
if (mask.length !== nPoints)
|
|
81
|
+
throw new Error(`Invalid mask length: expected ${nPoints}, got ${mask.length}`);
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
const optimalCount = mask.filter((x) => x).length;
|
|
85
|
+
if (optimalCount === 0)
|
|
86
|
+
throw new Error('No optimal points found');
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if (optimalCount === nPoints)
|
|
90
|
+
grok.shell.warning('All points are optimal - data may be degenerate');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
category('Pareto optimization', () => {
|
|
94
|
+
test(`Performance: ${DATASET_SIZE_LABEL}`, async () => {
|
|
95
|
+
let mask: boolean[] | null = null;
|
|
96
|
+
let error: Error | null = null;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const data = generateSyntheticData(ROWS_COUNT, COLS_COUNT);
|
|
100
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
101
|
+
mask = getParetoMask(data, sense, ROWS_COUNT);
|
|
102
|
+
validateParetoMask(mask, ROWS_COUNT);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
error = e as Error;
|
|
105
|
+
grok.shell.error(error.message);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
109
|
+
expect(error === null, true, error?.message ?? '');
|
|
110
|
+
}, {timeout: TIMEOUT});
|
|
111
|
+
|
|
112
|
+
// Tests for different optimization patterns
|
|
113
|
+
test(`Performance: ${DATASET_SIZE_LABEL}, all minimize`, async () => {
|
|
114
|
+
let mask: boolean[] | null = null;
|
|
115
|
+
let error: Error | null = null;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const data = generateSyntheticData(ROWS_COUNT, COLS_COUNT);
|
|
119
|
+
const sense = generateSense(COLS_COUNT, 'all-min');
|
|
120
|
+
mask = getParetoMask(data, sense, ROWS_COUNT);
|
|
121
|
+
validateParetoMask(mask, ROWS_COUNT);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
error = e as Error;
|
|
124
|
+
grok.shell.error(error.message);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
128
|
+
expect(error === null, true, error?.message ?? '');
|
|
129
|
+
}, {timeout: TIMEOUT});
|
|
130
|
+
|
|
131
|
+
test(`Performance: ${DATASET_SIZE_LABEL}, all maximize`, async () => {
|
|
132
|
+
let mask: boolean[] | null = null;
|
|
133
|
+
let error: Error | null = null;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const data = generateSyntheticData(ROWS_COUNT, COLS_COUNT);
|
|
137
|
+
const sense = generateSense(COLS_COUNT, 'all-max');
|
|
138
|
+
mask = getParetoMask(data, sense, ROWS_COUNT);
|
|
139
|
+
validateParetoMask(mask, ROWS_COUNT);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
error = e as Error;
|
|
142
|
+
grok.shell.error(error.message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
146
|
+
expect(error === null, true, error?.message ?? '');
|
|
147
|
+
}, {timeout: TIMEOUT});
|
|
148
|
+
|
|
149
|
+
// Tests with missing values
|
|
150
|
+
test(`Performance: ${DATASET_SIZE_LABEL} with 10% null indices`, async () => {
|
|
151
|
+
let mask: boolean[] | null = null;
|
|
152
|
+
let error: Error | null = null;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const data = generateSyntheticData(ROWS_COUNT, COLS_COUNT);
|
|
156
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
157
|
+
const nullIndices = generateNullIndices(ROWS_COUNT, 0.1);
|
|
158
|
+
mask = getParetoMask(data, sense, ROWS_COUNT, nullIndices);
|
|
159
|
+
validateParetoMask(mask, ROWS_COUNT);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
error = e as Error;
|
|
162
|
+
grok.shell.error(error.message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
166
|
+
expect(error === null, true, error?.message ?? '');
|
|
167
|
+
}, {timeout: TIMEOUT});
|
|
168
|
+
|
|
169
|
+
test(`Performance: ${DATASET_SIZE_LABEL} with 25% null indices`, async () => {
|
|
170
|
+
let mask: boolean[] | null = null;
|
|
171
|
+
let error: Error | null = null;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const data = generateSyntheticData(ROWS_COUNT, COLS_COUNT);
|
|
175
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
176
|
+
const nullIndices = generateNullIndices(ROWS_COUNT, 0.25);
|
|
177
|
+
mask = getParetoMask(data, sense, ROWS_COUNT, nullIndices);
|
|
178
|
+
validateParetoMask(mask, ROWS_COUNT);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
error = e as Error;
|
|
181
|
+
grok.shell.error(error.message);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
185
|
+
expect(error === null, true, error?.message ?? '');
|
|
186
|
+
}, {timeout: TIMEOUT});
|
|
187
|
+
|
|
188
|
+
// Edge cases
|
|
189
|
+
test('Edge case: Empty dataset', async () => {
|
|
190
|
+
let mask: boolean[] | null = null;
|
|
191
|
+
let error: Error | null = null;
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const data: NumericArray[] = [new Float32Array(0), new Float32Array(0)];
|
|
195
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
196
|
+
mask = getParetoMask(data, sense, 0);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
error = e as Error;
|
|
199
|
+
grok.shell.error(error.message);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
203
|
+
expect(mask!.length, 0, 'Empty dataset should return empty mask');
|
|
204
|
+
expect(error === null, true, error?.message ?? '');
|
|
205
|
+
}, {timeout: TIMEOUT});
|
|
206
|
+
|
|
207
|
+
test('Edge case: Single point', async () => {
|
|
208
|
+
let mask: boolean[] | null = null;
|
|
209
|
+
let error: Error | null = null;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const data: NumericArray[] = [new Float32Array([1.0]), new Float32Array([2.0])];
|
|
213
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
214
|
+
|
|
215
|
+
mask = getParetoMask(data, sense, 1);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
error = e as Error;
|
|
218
|
+
grok.shell.error(error.message);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
222
|
+
expect(mask!.length, 1, 'Single point dataset should return mask with one element');
|
|
223
|
+
expect(mask![0], true, 'Single point should be optimal');
|
|
224
|
+
expect(error === null, true, error?.message ?? '');
|
|
225
|
+
}, {timeout: TIMEOUT});
|
|
226
|
+
|
|
227
|
+
test('Edge case: All identical points', async () => {
|
|
228
|
+
let mask: boolean[] | null = null;
|
|
229
|
+
let error: Error | null = null;
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const nPoints = 100;
|
|
233
|
+
const data: NumericArray[] = [
|
|
234
|
+
new Float32Array(nPoints).fill(5.0),
|
|
235
|
+
new Float32Array(nPoints).fill(10.0),
|
|
236
|
+
];
|
|
237
|
+
const sense = generateSense(COLS_COUNT, 'mixed');
|
|
238
|
+
|
|
239
|
+
mask = getParetoMask(data, sense, nPoints);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
error = e as Error;
|
|
242
|
+
grok.shell.error(error.message);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
expect(mask !== null, true, 'Failed to compute Pareto mask');
|
|
246
|
+
expect(mask!.length, 100, 'Should return mask with correct length');
|
|
247
|
+
const optimalCount = mask!.filter((x) => x).length;
|
|
248
|
+
expect(optimalCount > 0, true, 'At least some identical points should be optimal');
|
|
249
|
+
expect(error === null, true, error?.message ?? '');
|
|
250
|
+
}, {timeout: TIMEOUT});
|
|
251
|
+
});
|