@sdeverywhere/check-core 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -68,51 +68,203 @@ var src_exports = {};
68
68
  __export(src_exports, {
69
69
  CheckDataCoordinator: () => CheckDataCoordinator,
70
70
  ComparisonDataCoordinator: () => ComparisonDataCoordinator,
71
- PerfRunner: () => PerfRunner,
72
71
  PerfStats: () => PerfStats,
73
72
  categorizeComparisonTestSummaries: () => categorizeComparisonTestSummaries,
74
73
  checkReportFromSummary: () => checkReportFromSummary,
75
74
  checkSummaryFromReport: () => checkSummaryFromReport,
76
75
  comparisonSummaryFromReport: () => comparisonSummaryFromReport,
76
+ createCheckDataCoordinator: () => createCheckDataCoordinator,
77
+ createCheckDataCoordinatorForTests: () => createCheckDataCoordinatorForTests,
78
+ createComparisonDataCoordinator: () => createComparisonDataCoordinator,
77
79
  createConfig: () => createConfig,
78
80
  datasetMessage: () => datasetMessage,
81
+ decodeImplVars: () => decodeImplVars,
79
82
  diffDatasets: () => diffDatasets,
80
83
  diffGraphs: () => diffGraphs,
84
+ encodeImplVars: () => encodeImplVars,
81
85
  getScoresForTestSummaries: () => getScoresForTestSummaries,
82
86
  predicateMessage: () => predicateMessage,
87
+ runPerf: () => runPerf,
83
88
  runSuite: () => runSuite,
89
+ runTrace: () => runTrace,
84
90
  scenarioMessage: () => scenarioMessage,
85
- suiteSummaryFromReport: () => suiteSummaryFromReport
91
+ suiteSummaryFromReport: () => suiteSummaryFromReport,
92
+ testSummaryFromReport: () => testSummaryFromReport
86
93
  });
87
94
  module.exports = __toCommonJS(src_exports);
88
95
 
96
+ // src/bundle/impl-vars-codec.ts
97
+ function encodeImplVars(input) {
98
+ const subscripts = [];
99
+ const variables = [];
100
+ const varTypes = [];
101
+ const varInstances = {};
102
+ const subscriptMap = /* @__PURE__ */ new Map();
103
+ const variableMap = /* @__PURE__ */ new Map();
104
+ const varTypeMap = /* @__PURE__ */ new Map();
105
+ for (const [groupKey, implVars] of Object.entries(input)) {
106
+ const instances = [];
107
+ for (const implVar of implVars) {
108
+ const varIdInfo = parseSubscripts(implVar.varId);
109
+ const varNameInfo = parseSubscripts(implVar.varName);
110
+ let varTypeIndex = varTypeMap.get(implVar.varType);
111
+ if (varTypeIndex === void 0) {
112
+ varTypeIndex = varTypes.length;
113
+ varTypes.push(implVar.varType);
114
+ varTypeMap.set(implVar.varType, varTypeIndex);
115
+ }
116
+ let variableIndex = variableMap.get(implVar.varIndex);
117
+ if (variableIndex === void 0) {
118
+ variableIndex = variables.length;
119
+ variables.push({
120
+ n: varNameInfo.base,
121
+ i: varIdInfo.base,
122
+ x: implVar.varIndex
123
+ });
124
+ variableMap.set(implVar.varIndex, variableIndex);
125
+ }
126
+ let subscriptIndices;
127
+ let subscriptValues;
128
+ if (varIdInfo.subscripts.length > 0) {
129
+ subscriptIndices = [];
130
+ subscriptValues = implVar.subscriptIndices || [];
131
+ for (let i = 0; i < varIdInfo.subscripts.length; i++) {
132
+ const subscriptId = varIdInfo.subscripts[i];
133
+ const subscriptName = varNameInfo.subscripts[i];
134
+ let subscriptIndex = subscriptMap.get(subscriptId);
135
+ if (subscriptIndex === void 0) {
136
+ subscriptIndex = subscripts.length;
137
+ subscripts.push({
138
+ n: subscriptName,
139
+ i: subscriptId
140
+ });
141
+ subscriptMap.set(subscriptId, subscriptIndex);
142
+ }
143
+ subscriptIndices.push(subscriptIndex);
144
+ }
145
+ }
146
+ const instance = [varTypeIndex, variableIndex];
147
+ if (subscriptIndices) {
148
+ instance.push(...subscriptIndices, ...subscriptValues);
149
+ }
150
+ instances.push(instance);
151
+ }
152
+ varInstances[groupKey] = instances;
153
+ }
154
+ return {
155
+ subscripts,
156
+ variables,
157
+ varTypes,
158
+ varInstances
159
+ };
160
+ }
161
+ function decodeImplVars(encoded) {
162
+ const result = {};
163
+ for (const [groupKey, instances] of Object.entries(encoded.varInstances)) {
164
+ const implVars = [];
165
+ for (const instance of instances) {
166
+ const varType = encoded.varTypes[instance[0]];
167
+ const variable = encoded.variables[instance[1]];
168
+ let varId = variable.i;
169
+ let varName = variable.n;
170
+ if (instance.length > 2) {
171
+ const subscriptIds = [];
172
+ const subscriptNames = [];
173
+ const subscriptCount = (instance.length - 2) / 2;
174
+ const subscriptIndices = instance.slice(2, 2 + subscriptCount);
175
+ for (const subscriptIndex of subscriptIndices) {
176
+ const subscript = encoded.subscripts[subscriptIndex];
177
+ subscriptIds.push(subscript.i);
178
+ subscriptNames.push(subscript.n);
179
+ }
180
+ varId += `[${subscriptIds.join(",")}]`;
181
+ varName += `[${subscriptNames.join(",")}]`;
182
+ }
183
+ const implVar = {
184
+ varId,
185
+ varName,
186
+ varType,
187
+ varIndex: variable.x,
188
+ subscriptIndices: instance.length > 2 ? instance.slice(2 + (instance.length - 2) / 2) : void 0
189
+ };
190
+ implVars.push(implVar);
191
+ }
192
+ result[groupKey] = implVars;
193
+ }
194
+ return result;
195
+ }
196
+ function parseSubscripts(text) {
197
+ const bracketIndex = text.indexOf("[");
198
+ if (bracketIndex === -1) {
199
+ return { base: text, subscripts: [] };
200
+ }
201
+ const base = text.substring(0, bracketIndex);
202
+ const subscriptText = text.substring(bracketIndex + 1, text.lastIndexOf("]"));
203
+ const subscripts = subscriptText.split(",").map((s) => s.trim());
204
+ return { base, subscripts };
205
+ }
206
+
89
207
  // src/_shared/task-queue.ts
90
- var TaskQueue = class {
91
- constructor(processor) {
92
- this.processor = processor;
208
+ var TaskQueue = class _TaskQueue {
209
+ /**
210
+ * @param executors The map of available task executors.
211
+ */
212
+ constructor(executors) {
213
+ this.executors = executors;
93
214
  /** The queue of task keys, most recent at front. */
94
215
  this.taskKeyQueue = [];
95
216
  /** The map of tasks. */
96
217
  this.taskMap = /* @__PURE__ */ new Map();
218
+ // /** The set of keys for the executors that are currently processing tasks. */
219
+ // private readonly activeExecutorKeys: Set<TaskExecutorKey> = new Set()
220
+ /** The idle event listeners. */
221
+ this.idleListeners = [];
97
222
  /** Whether tasks are being processed. */
98
223
  this.processing = false;
99
224
  /** Whether `shutdown` has been called. */
100
225
  this.stopped = false;
101
226
  }
102
- addTask(key, input, onComplete) {
227
+ /**
228
+ * Initialize the shared `TaskQueue` instance.
229
+ *
230
+ * @param executors The map of available task executors.
231
+ */
232
+ static initialize(executors) {
233
+ if (executors.size === 0) {
234
+ throw new Error("Must provide at least one executor");
235
+ }
236
+ this.instance = new _TaskQueue(executors);
237
+ }
238
+ /**
239
+ * Get the shared `TaskQueue` instance.
240
+ */
241
+ static getInstance() {
242
+ if (!this.instance) {
243
+ throw new Error("TaskQueue not initialized; must call `initialize` first");
244
+ }
245
+ return this.instance;
246
+ }
247
+ /**
248
+ * Add a task to the queue.
249
+ *
250
+ * @param task The task to add.
251
+ */
252
+ addTask(task) {
103
253
  if (this.stopped) {
104
254
  return;
105
255
  }
106
- if (this.taskMap.has(key)) {
107
- throw new Error(`Task already added for key ${key}`);
256
+ if (this.taskMap.has(task.key)) {
257
+ throw new Error(`Task already added for key ${task.key}`);
108
258
  }
109
- this.taskKeyQueue.push(key);
110
- this.taskMap.set(key, {
111
- input,
112
- onComplete
113
- });
259
+ this.taskKeyQueue.push(task.key);
260
+ this.taskMap.set(task.key, task);
114
261
  this.processTasksIfNeeded();
115
262
  }
263
+ /**
264
+ * Cancel a task.
265
+ *
266
+ * @param taskKey The key of the task to cancel.
267
+ */
116
268
  cancelTask(taskKey) {
117
269
  const index = this.taskKeyQueue.indexOf(taskKey);
118
270
  if (index >= 0) {
@@ -120,9 +272,37 @@ var TaskQueue = class {
120
272
  }
121
273
  this.taskMap.delete(taskKey);
122
274
  }
275
+ /**
276
+ * Add an idle listener.
277
+ *
278
+ * @param listener The listener to add.
279
+ */
280
+ onIdle(listener) {
281
+ this.idleListeners.push(listener);
282
+ }
283
+ /**
284
+ * Remove an idle listener.
285
+ *
286
+ * @param listener The listener to remove.
287
+ */
288
+ removeIdleListener(listener) {
289
+ this.idleListeners.splice(this.idleListeners.indexOf(listener), 1);
290
+ }
291
+ /**
292
+ * Notify the idle listeners.
293
+ *
294
+ * @param error The error to notify the listeners with.
295
+ */
296
+ notifyIdle(error) {
297
+ for (const listener of this.idleListeners) {
298
+ listener(error);
299
+ }
300
+ }
301
+ /**
302
+ * Shutdown the task queue, cancelling all pending tasks.
303
+ */
123
304
  shutdown() {
124
305
  this.stopped = true;
125
- this.processing = false;
126
306
  this.taskKeyQueue.length = 0;
127
307
  this.taskMap.clear();
128
308
  }
@@ -130,75 +310,98 @@ var TaskQueue = class {
130
310
  if (!this.stopped && !this.processing) {
131
311
  this.processing = true;
132
312
  setTimeout(() => {
133
- this.processNextTask();
313
+ this.processNextTasks();
134
314
  });
135
315
  }
136
316
  }
137
- processNextTask() {
317
+ processNextTasks() {
138
318
  return __async(this, null, function* () {
139
- var _a, _b;
140
- const taskKey = this.taskKeyQueue.shift();
141
- if (!taskKey) {
319
+ const taskKeys = this.taskKeyQueue.splice(0, this.executors.size);
320
+ if (taskKeys.length === 0) {
321
+ this.processing = false;
322
+ if (!this.stopped) {
323
+ this.notifyIdle();
324
+ }
142
325
  return;
143
326
  }
144
- const task = this.taskMap.get(taskKey);
145
- if (task) {
327
+ const executeCalls = [];
328
+ const availableExecutorKeys = Array.from(this.executors.keys());
329
+ for (const taskKey of taskKeys) {
330
+ const task = this.taskMap.get(taskKey);
331
+ if (!task) {
332
+ continue;
333
+ }
146
334
  this.taskMap.delete(taskKey);
147
- } else {
148
- return;
335
+ const executorKey = availableExecutorKeys.shift();
336
+ const executor = this.executors.get(executorKey);
337
+ if (!executor) {
338
+ throw new Error(`No executor found for key ${executorKey}`);
339
+ }
340
+ executeCalls.push(executor.execute(task));
149
341
  }
150
- let output;
151
342
  try {
152
- output = yield this.processor.process(task.input);
343
+ yield Promise.all(executeCalls);
153
344
  } catch (e) {
154
345
  if (!this.stopped) {
155
346
  this.shutdown();
156
- (_a = this.onIdle) == null ? void 0 : _a.call(this, e);
347
+ this.notifyIdle(e);
157
348
  }
158
349
  return;
159
350
  }
160
- task.onComplete(output);
161
351
  if (this.taskKeyQueue.length > 0) {
162
352
  setTimeout(() => {
163
- this.processNextTask();
353
+ this.processNextTasks();
164
354
  });
165
355
  } else {
166
356
  this.processing = false;
167
357
  if (!this.stopped) {
168
- (_b = this.onIdle) == null ? void 0 : _b.call(this);
358
+ this.notifyIdle();
169
359
  }
170
360
  }
171
361
  });
172
362
  }
173
363
  };
364
+ function createExecutor(bundleModelL, bundleModelR) {
365
+ return {
366
+ execute: (task) => __async(this, null, function* () {
367
+ return task.process({
368
+ L: bundleModelL,
369
+ R: bundleModelR
370
+ });
371
+ })
372
+ };
373
+ }
174
374
 
175
375
  // src/check/check-data-coordinator.ts
176
376
  var CheckDataCoordinator = class {
177
- constructor(bundleModel) {
178
- this.bundleModel = bundleModel;
179
- this.taskQueue = new TaskQueue({
180
- process: (request) => __async(this, null, function* () {
181
- const result = yield this.bundleModel.getDatasetsForScenario(request.scenarioSpec, [request.datasetKey]);
182
- const dataset = result.datasetMap.get(request.datasetKey);
183
- return {
184
- dataset
185
- };
186
- })
187
- });
377
+ constructor(taskQueue) {
378
+ this.taskQueue = taskQueue;
188
379
  }
189
380
  requestDataset(requestKey, scenarioSpec, datasetKey, onResponse) {
190
- const request = {
191
- scenarioSpec,
192
- datasetKey
381
+ const task = {
382
+ key: requestKey,
383
+ kind: "check-data-coordinator",
384
+ process: (bundleModels) => __async(this, null, function* () {
385
+ const bundleModelR = bundleModels.R;
386
+ const result = yield bundleModelR.getDatasetsForScenario(scenarioSpec, [datasetKey]);
387
+ const dataset = result.datasetMap.get(datasetKey);
388
+ onResponse(dataset);
389
+ })
193
390
  };
194
- this.taskQueue.addTask(requestKey, request, (response) => {
195
- onResponse(response.dataset);
196
- });
391
+ this.taskQueue.addTask(task);
197
392
  }
198
393
  cancelRequest(key) {
199
394
  this.taskQueue.cancelTask(key);
200
395
  }
201
396
  };
397
+ function createCheckDataCoordinator() {
398
+ return new CheckDataCoordinator(TaskQueue.getInstance());
399
+ }
400
+ function createCheckDataCoordinatorForTests(bundleModel) {
401
+ const executor = createExecutor(void 0, bundleModel);
402
+ const taskQueue = new TaskQueue(/* @__PURE__ */ new Map([["test-executor-0", executor]]));
403
+ return new CheckDataCoordinator(taskQueue);
404
+ }
202
405
 
203
406
  // src/check/check-report.ts
204
407
  var import_assert_never2 = __toESM(require("assert-never"), 1);
@@ -260,6 +463,10 @@ function buildCheckReport(checkPlan, checkResults) {
260
463
  testStatus = "failed";
261
464
  scenarioStatus = "failed";
262
465
  datasetStatus = "failed";
466
+ } else if (checkResult.status === "skipped" && testStatus === "passed") {
467
+ testStatus = "skipped";
468
+ scenarioStatus = "skipped";
469
+ datasetStatus = "skipped";
263
470
  }
264
471
  }
265
472
  predicateReports.push(predicateReport(predicatePlan, checkKey, checkResult));
@@ -1195,16 +1402,6 @@ function actionForPredicate(predicateSpec) {
1195
1402
  };
1196
1403
  }
1197
1404
 
1198
- // src/_shared/combo.ts
1199
- function cartesianProductOf(arr) {
1200
- return arr.reduce(
1201
- (a, b) => {
1202
- return a.map((x) => b.map((y) => x.concat([y]))).reduce((v, w) => v.concat(w), []);
1203
- },
1204
- [[]]
1205
- );
1206
- }
1207
-
1208
1405
  // src/check/check-dataset.ts
1209
1406
  function expandDatasets(modelSpec, datasetSpec) {
1210
1407
  var _a;
@@ -1233,25 +1430,10 @@ function expandDatasets(modelSpec, datasetSpec) {
1233
1430
  name: match.outputVar.varName
1234
1431
  });
1235
1432
  } else if (match.implVar) {
1236
- const implVar = match.implVar;
1237
- if (implVar.dimensions.length > 0) {
1238
- const baseDatasetKey = match.datasetKey;
1239
- const subscripts = [...implVar.dimensions.map((dim) => dim.subscripts)];
1240
- const subscriptCombos = cartesianProductOf(subscripts);
1241
- for (const subscriptCombo of subscriptCombos) {
1242
- const subIdParts = subscriptCombo.map((sub) => `[${sub.id}]`).join("");
1243
- const subNameParts = subscriptCombo.map((sub) => sub.name).join(",");
1244
- checkDatasets.push({
1245
- datasetKey: `${baseDatasetKey}${subIdParts}`,
1246
- name: `${implVar.varName}[${subNameParts}]`
1247
- });
1248
- }
1249
- } else {
1250
- checkDatasets.push({
1251
- datasetKey: match.datasetKey,
1252
- name: implVar.varName
1253
- });
1254
- }
1433
+ checkDatasets.push({
1434
+ datasetKey: match.datasetKey,
1435
+ name: match.implVar.varName
1436
+ });
1255
1437
  }
1256
1438
  }
1257
1439
  return checkDatasets;
@@ -1659,12 +1841,18 @@ var CheckPlanner = class {
1659
1841
  this.dataRefs = /* @__PURE__ */ new Map();
1660
1842
  this.checkKey = 1;
1661
1843
  }
1662
- addAllChecks(checkSpec, simplifyScenarios) {
1844
+ addAllChecks(checkSpec, skipChecks) {
1845
+ function skipCheckKey(groupName, testName) {
1846
+ return `${groupName.toLowerCase()} :: ${testName.toLowerCase()}`;
1847
+ }
1848
+ const skipChecksSet = new Set(skipChecks.map((check) => skipCheckKey(check.groupName, check.testName)));
1663
1849
  for (const groupSpec of checkSpec.groups) {
1664
1850
  const groupName = groupSpec.describe;
1665
1851
  const planTests = [];
1666
1852
  for (const testSpec of groupSpec.tests) {
1667
1853
  const testName = testSpec.it;
1854
+ const shouldSkip = skipChecksSet.has(skipCheckKey(groupName, testName));
1855
+ const simplifyScenarios = false;
1668
1856
  const checkScenarios = expandScenarios(this.modelSpec, testSpec.scenarios || [], simplifyScenarios);
1669
1857
  const checkDatasets = [];
1670
1858
  for (const datasetSpec of testSpec.datasets) {
@@ -1705,7 +1893,8 @@ var CheckPlanner = class {
1705
1893
  scenario: checkScenario,
1706
1894
  dataset: checkDataset,
1707
1895
  action: checkAction,
1708
- dataRefs
1896
+ dataRefs,
1897
+ skip: shouldSkip
1709
1898
  });
1710
1899
  }
1711
1900
  planDatasets.push({
@@ -1842,6 +2031,7 @@ function checkSummaryFromReport(checkReport) {
1842
2031
  break;
1843
2032
  case "failed":
1844
2033
  case "error":
2034
+ case "skipped":
1845
2035
  predicateSummaries.push({
1846
2036
  checkKey: predicate.checkKey,
1847
2037
  result: predicate.result
@@ -1859,14 +2049,14 @@ function checkSummaryFromReport(checkReport) {
1859
2049
  predicateSummaries
1860
2050
  };
1861
2051
  }
1862
- function checkReportFromSummary(checkConfig, checkSummary) {
2052
+ function checkReportFromSummary(checkConfig, checkSummary, skipChecks = []) {
1863
2053
  const checkSpecResult = parseTestYaml(checkConfig.tests);
1864
2054
  if (checkSpecResult.isErr()) {
1865
2055
  return void 0;
1866
2056
  }
1867
2057
  const checkSpec = checkSpecResult.value;
1868
- const checkPlanner = new CheckPlanner(checkConfig.bundle.model.modelSpec);
1869
- checkPlanner.addAllChecks(checkSpec, false);
2058
+ const checkPlanner = new CheckPlanner(checkConfig.bundle.modelSpec);
2059
+ checkPlanner.addAllChecks(checkSpec, skipChecks);
1870
2060
  const checkPlan = checkPlanner.buildPlan();
1871
2061
  const checkResults = /* @__PURE__ */ new Map();
1872
2062
  for (const predicateSummary of checkSummary.predicateSummaries) {
@@ -1876,98 +2066,115 @@ function checkReportFromSummary(checkConfig, checkSummary) {
1876
2066
  }
1877
2067
 
1878
2068
  // src/comparison/run/comparison-data-coordinator.ts
1879
- var import_assert_never7 = require("assert-never");
1880
2069
  var ComparisonDataCoordinator = class {
1881
- constructor(bundleModelL, bundleModelR) {
1882
- this.bundleModelL = bundleModelL;
1883
- this.bundleModelR = bundleModelR;
1884
- this.taskQueue = new TaskQueue({
1885
- process: (request) => __async(this, null, function* () {
1886
- switch (request.kind) {
1887
- case "dataset":
1888
- return this.processDatasetRequest(request);
1889
- case "graph-data":
1890
- return this.processGraphDataRequest(request);
1891
- default:
1892
- (0, import_assert_never7.assertNever)(request);
2070
+ constructor(taskQueue) {
2071
+ this.taskQueue = taskQueue;
2072
+ }
2073
+ /**
2074
+ * Request datasets from the two models.
2075
+ *
2076
+ * @param requestKey The unique key for the request.
2077
+ * @param sourceL The source of the first ("left") dataset. If "left", the datasets will
2078
+ * be fetched from the "left" bundle's model, otherwise they will be fetched from the
2079
+ * "right" bundle's model.
2080
+ * @param scenarioSpecL The scenario used for the first ("left") model of the comparison.
2081
+ * @param sourceR The source of the second ("right") dataset. If "left", the datasets
2082
+ * will be fetched from the "left" bundle's model, otherwise they will be fetched from
2083
+ * the "right" bundle's model.
2084
+ * @param scenarioSpecR The scenario used for the second ("right") model of the comparison.
2085
+ * @param graphId The keys of the datasets to be fetched.
2086
+ * @param onResponse The callback that will be called with the dataset maps.
2087
+ */
2088
+ requestDatasetMaps(requestKey, sourceL, scenarioSpecL, sourceR, scenarioSpecR, datasetKeys, onResponse) {
2089
+ function fetchDatasets(bundleModel, scenarioSpec) {
2090
+ return __async(this, null, function* () {
2091
+ if (scenarioSpec) {
2092
+ return bundleModel.getDatasetsForScenario(scenarioSpec, datasetKeys);
2093
+ } else {
2094
+ return void 0;
2095
+ }
2096
+ });
2097
+ }
2098
+ const task = {
2099
+ key: requestKey,
2100
+ kind: "comparison-data-coordinator",
2101
+ process: (bundleModels) => __async(this, null, function* () {
2102
+ const modelL = sourceL === "left" ? bundleModels.L : bundleModels.R;
2103
+ const modelR = sourceR === "left" ? bundleModels.L : bundleModels.R;
2104
+ let resultL;
2105
+ let resultR;
2106
+ if (modelL === modelR) {
2107
+ resultL = yield fetchDatasets(modelL, scenarioSpecL);
2108
+ resultR = yield fetchDatasets(modelR, scenarioSpecR);
2109
+ } else {
2110
+ const results = yield Promise.all([
2111
+ fetchDatasets(modelL, scenarioSpecL),
2112
+ fetchDatasets(modelR, scenarioSpecR)
2113
+ ]);
2114
+ resultL = results[0];
2115
+ resultR = results[1];
1893
2116
  }
2117
+ onResponse(resultL == null ? void 0 : resultL.datasetMap, resultR == null ? void 0 : resultR.datasetMap);
1894
2118
  })
1895
- });
1896
- }
1897
- processDatasetRequest(request) {
1898
- return __async(this, null, function* () {
1899
- function fetchDatasets(bundleModel, scenarioSpec) {
1900
- return __async(this, null, function* () {
1901
- if (scenarioSpec) {
1902
- return bundleModel.getDatasetsForScenario(scenarioSpec, request.datasetKeys);
1903
- } else {
1904
- return void 0;
1905
- }
1906
- });
1907
- }
1908
- const [resultL, resultR] = yield Promise.all([
1909
- fetchDatasets(this.bundleModelL, request.scenarioSpecL),
1910
- fetchDatasets(this.bundleModelR, request.scenarioSpecR)
1911
- ]);
1912
- return {
1913
- kind: "dataset",
1914
- datasetMapL: resultL == null ? void 0 : resultL.datasetMap,
1915
- datasetMapR: resultR == null ? void 0 : resultR.datasetMap
1916
- };
1917
- });
1918
- }
1919
- processGraphDataRequest(request) {
1920
- return __async(this, null, function* () {
1921
- function fetchGraphData(bundleModel, scenarioSpec) {
1922
- return __async(this, null, function* () {
1923
- if (scenarioSpec) {
1924
- return bundleModel.getGraphDataForScenario(scenarioSpec, request.graphId);
1925
- } else {
1926
- return void 0;
1927
- }
1928
- });
1929
- }
1930
- const [graphDataL, graphDataR] = yield Promise.all([
1931
- fetchGraphData(this.bundleModelL, request.scenarioSpecL),
1932
- fetchGraphData(this.bundleModelR, request.scenarioSpecR)
1933
- ]);
1934
- return {
1935
- kind: "graph-data",
1936
- graphDataL,
1937
- graphDataR
1938
- };
1939
- });
1940
- }
1941
- requestDatasetMaps(requestKey, scenarioSpecL, scenarioSpecR, datasetKeys, onResponse) {
1942
- const request = {
1943
- kind: "dataset",
1944
- scenarioSpecL,
1945
- scenarioSpecR,
1946
- datasetKeys
1947
2119
  };
1948
- this.taskQueue.addTask(requestKey, request, (response) => {
1949
- if (response.kind === "dataset") {
1950
- onResponse(response.datasetMapL, response.datasetMapR);
1951
- }
1952
- });
2120
+ this.taskQueue.addTask(task);
1953
2121
  }
1954
- requestGraphData(requestKey, scenarioSpecL, scenarioSpecR, graphId, onResponse) {
1955
- const request = {
1956
- kind: "graph-data",
1957
- scenarioSpecL,
1958
- scenarioSpecR,
1959
- graphId
2122
+ /**
2123
+ * Request graph data from the two models.
2124
+ *
2125
+ * @param requestKey The unique key for the request.
2126
+ * @param sourceL The source of the first ("left") dataset. If "left", the datasets will
2127
+ * be fetched from the "left" bundle's model, otherwise they will be fetched from the
2128
+ * "right" bundle's model.
2129
+ * @param scenarioSpecL The scenario used for the first ("left") model of the comparison.
2130
+ * @param sourceR The source of the second ("right") dataset. If "left", the datasets
2131
+ * will be fetched from the "left" bundle's model, otherwise they will be fetched from
2132
+ * the "right" bundle's model.
2133
+ * @param scenarioSpecR The scenario used for the second ("right") model of the comparison.
2134
+ * @param graphId The ID of the graph for which data will be fetched.
2135
+ * @param onResponse The callback that will be called with the graph data.
2136
+ */
2137
+ requestGraphData(requestKey, sourceL, scenarioSpecL, sourceR, scenarioSpecR, graphId, onResponse) {
2138
+ function fetchGraphData(bundleModel, scenarioSpec) {
2139
+ return __async(this, null, function* () {
2140
+ if (scenarioSpec) {
2141
+ return bundleModel.getGraphDataForScenario(scenarioSpec, graphId);
2142
+ } else {
2143
+ return void 0;
2144
+ }
2145
+ });
2146
+ }
2147
+ const task = {
2148
+ key: requestKey,
2149
+ kind: "comparison-data-coordinator",
2150
+ process: (bundleModels) => __async(this, null, function* () {
2151
+ const modelL = sourceL === "left" ? bundleModels.L : bundleModels.R;
2152
+ const modelR = sourceR === "left" ? bundleModels.L : bundleModels.R;
2153
+ let graphDataL;
2154
+ let graphDataR;
2155
+ if (modelL === modelR) {
2156
+ graphDataL = yield fetchGraphData(modelL, scenarioSpecL);
2157
+ graphDataR = yield fetchGraphData(modelR, scenarioSpecR);
2158
+ } else {
2159
+ const results = yield Promise.all([
2160
+ fetchGraphData(modelL, scenarioSpecL),
2161
+ fetchGraphData(modelR, scenarioSpecR)
2162
+ ]);
2163
+ graphDataL = results[0];
2164
+ graphDataR = results[1];
2165
+ }
2166
+ onResponse(graphDataL, graphDataR);
2167
+ })
1960
2168
  };
1961
- this.taskQueue.addTask(requestKey, request, (response) => {
1962
- if (response.kind === "graph-data") {
1963
- onResponse(response.graphDataL, response.graphDataR);
1964
- }
1965
- });
2169
+ this.taskQueue.addTask(task);
1966
2170
  }
1967
2171
  cancelRequest(key) {
1968
2172
  this.taskQueue.cancelTask(key);
1969
2173
  }
1970
2174
  };
2175
+ function createComparisonDataCoordinator() {
2176
+ return new ComparisonDataCoordinator(TaskQueue.getInstance());
2177
+ }
1971
2178
 
1972
2179
  // src/comparison/diff-datasets/diff-datasets.ts
1973
2180
  function diffDatasets(datasetL, datasetR) {
@@ -2117,14 +2324,14 @@ function diffGraphs(graphL, graphR, scenarioKey, testSummaries) {
2117
2324
 
2118
2325
  // src/comparison/report/comparison-reporting.ts
2119
2326
  function comparisonSummaryFromReport(comparisonReport) {
2327
+ var _a, _b;
2120
2328
  const terseSummaries = [];
2121
2329
  for (const r of comparisonReport.testReports) {
2122
- if (r.diffReport.validity === "both" && r.diffReport.maxDiff > 0) {
2123
- terseSummaries.push({
2124
- s: r.scenarioKey,
2125
- d: r.datasetKey,
2126
- md: r.diffReport.maxDiff
2127
- });
2330
+ const baselineMaxDiff = (_a = r.baselineDiffReport) == null ? void 0 : _a.maxDiff;
2331
+ const baselineAvgDiff = (_b = r.baselineDiffReport) == null ? void 0 : _b.avgDiff;
2332
+ const summary = testSummaryFromReport(r, baselineMaxDiff, baselineAvgDiff);
2333
+ if (summary) {
2334
+ terseSummaries.push(summary);
2128
2335
  }
2129
2336
  }
2130
2337
  return {
@@ -2133,6 +2340,43 @@ function comparisonSummaryFromReport(comparisonReport) {
2133
2340
  perfReportR: comparisonReport.perfReportR
2134
2341
  };
2135
2342
  }
2343
+ function testSummaryFromReport(r, baselineMaxDiff, baselineAvgDiff) {
2344
+ var _a;
2345
+ function baselineRelativeDiff(diffValue, baselineDiffValue) {
2346
+ if (baselineDiffValue !== void 0) {
2347
+ const epsilon = 1e-6;
2348
+ if (baselineDiffValue === 0) {
2349
+ baselineDiffValue = epsilon;
2350
+ }
2351
+ return diffValue / baselineDiffValue;
2352
+ } else {
2353
+ if (diffValue === 0) {
2354
+ return 0;
2355
+ } else {
2356
+ return 1;
2357
+ }
2358
+ }
2359
+ }
2360
+ if (r.diffReport === void 0) {
2361
+ return {
2362
+ s: r.scenarioKey,
2363
+ d: r.datasetKey
2364
+ };
2365
+ } else if (((_a = r.diffReport) == null ? void 0 : _a.validity) === "both" && r.diffReport.maxDiff > 0) {
2366
+ const maxDiffRelativeToBaseline = baselineRelativeDiff(r.diffReport.maxDiff, baselineMaxDiff);
2367
+ const avgDiffRelativeToBaseline = baselineRelativeDiff(r.diffReport.avgDiff, baselineAvgDiff);
2368
+ return {
2369
+ s: r.scenarioKey,
2370
+ d: r.datasetKey,
2371
+ md: r.diffReport.maxDiff,
2372
+ ad: r.diffReport.avgDiff,
2373
+ mdb: maxDiffRelativeToBaseline,
2374
+ adb: avgDiffRelativeToBaseline
2375
+ };
2376
+ } else {
2377
+ return void 0;
2378
+ }
2379
+ }
2136
2380
  function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2137
2381
  const existingSummaries = /* @__PURE__ */ new Map();
2138
2382
  for (const summary of terseSummaries) {
@@ -2145,24 +2389,33 @@ function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2145
2389
  for (const datasetKey of datasetKeys) {
2146
2390
  const key = `${scenario.key}::${datasetKey}`;
2147
2391
  const existingSummary = existingSummaries.get(key);
2148
- const maxDiff = (existingSummary == null ? void 0 : existingSummary.md) || 0;
2149
- allTestSummaries.push({
2150
- s: scenario.key,
2151
- d: datasetKey,
2152
- md: maxDiff
2153
- });
2392
+ if (existingSummary) {
2393
+ allTestSummaries.push(existingSummary);
2394
+ } else {
2395
+ allTestSummaries.push({
2396
+ s: scenario.key,
2397
+ d: datasetKey,
2398
+ md: 0,
2399
+ ad: 0,
2400
+ mdb: 0,
2401
+ adb: 0
2402
+ });
2403
+ }
2154
2404
  }
2155
2405
  }
2156
2406
  return allTestSummaries;
2157
2407
  }
2158
2408
 
2159
2409
  // src/comparison/report/buckets.ts
2160
- function getBucketIndex(diffPct, thresholds) {
2161
- if (diffPct === 0) {
2410
+ function getBucketIndex(diff, thresholds) {
2411
+ if (diff === void 0) {
2412
+ return thresholds.length + 2;
2413
+ }
2414
+ if (diff === 0) {
2162
2415
  return 0;
2163
2416
  }
2164
2417
  for (let i = 0; i < thresholds.length; i++) {
2165
- if (diffPct < thresholds[i]) {
2418
+ if (diff < thresholds[i]) {
2166
2419
  return i + 1;
2167
2420
  }
2168
2421
  }
@@ -2170,14 +2423,32 @@ function getBucketIndex(diffPct, thresholds) {
2170
2423
  }
2171
2424
 
2172
2425
  // src/comparison/report/comparison-group-scores.ts
2173
- function getScoresForTestSummaries(testSummaries, thresholds) {
2174
- const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2175
- const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2426
+ function getScoresForTestSummaries(testSummaries, thresholds, sortMode) {
2427
+ const diffCountByBucket = Array(thresholds.length + 3).fill(0);
2428
+ const totalDiffByBucket = Array(thresholds.length + 3).fill(0);
2176
2429
  let totalDiffCount = 0;
2430
+ let valueKey;
2431
+ switch (sortMode) {
2432
+ case "max-diff":
2433
+ valueKey = "md";
2434
+ break;
2435
+ case "avg-diff":
2436
+ valueKey = "ad";
2437
+ break;
2438
+ case "max-diff-relative":
2439
+ valueKey = "mdb";
2440
+ break;
2441
+ case "avg-diff-relative":
2442
+ valueKey = "adb";
2443
+ break;
2444
+ }
2177
2445
  for (const testSummary of testSummaries) {
2178
- const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2446
+ const value = testSummary[valueKey];
2447
+ const bucketIndex = getBucketIndex(value, thresholds);
2179
2448
  diffCountByBucket[bucketIndex]++;
2180
- totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2449
+ if (value !== void 0) {
2450
+ totalDiffByBucket[bucketIndex] += value;
2451
+ }
2181
2452
  totalDiffCount++;
2182
2453
  }
2183
2454
  let diffPercentByBucket;
@@ -2188,20 +2459,20 @@ function getScoresForTestSummaries(testSummaries, thresholds) {
2188
2459
  }
2189
2460
  return {
2190
2461
  totalDiffCount,
2191
- totalMaxDiffByBucket,
2462
+ totalDiffByBucket,
2192
2463
  diffCountByBucket,
2193
2464
  diffPercentByBucket
2194
2465
  };
2195
2466
  }
2196
2467
 
2197
2468
  // src/comparison/report/comparison-grouping.ts
2198
- var import_assert_never8 = require("assert-never");
2199
- function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2469
+ var import_assert_never7 = require("assert-never");
2470
+ function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries, sortMode) {
2200
2471
  const allTestSummaries = restoreFromTerseSummaries(comparisonConfig, terseSummaries);
2201
2472
  const groupsByScenario = groupComparisonTestSummaries(allTestSummaries, "by-scenario");
2202
- const byScenario = categorizeComparisonGroups(comparisonConfig, [...groupsByScenario.values()]);
2473
+ const byScenario = categorizeComparisonGroups(comparisonConfig, [...groupsByScenario.values()], sortMode);
2203
2474
  const groupsByDataset = groupComparisonTestSummaries(allTestSummaries, "by-dataset");
2204
- const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()]);
2475
+ const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()], sortMode);
2205
2476
  return {
2206
2477
  allTestSummaries,
2207
2478
  byScenario,
@@ -2220,7 +2491,7 @@ function groupComparisonTestSummaries(testSummaries, groupKind) {
2220
2491
  groupKey = testSummary.s;
2221
2492
  break;
2222
2493
  default:
2223
- (0, import_assert_never8.assertNever)(groupKind);
2494
+ (0, import_assert_never7.assertNever)(groupKind);
2224
2495
  }
2225
2496
  const group = groups.get(groupKey);
2226
2497
  if (group) {
@@ -2235,7 +2506,7 @@ function groupComparisonTestSummaries(testSummaries, groupKind) {
2235
2506
  }
2236
2507
  return groups;
2237
2508
  }
2238
- function categorizeComparisonGroups(comparisonConfig, allGroups) {
2509
+ function categorizeComparisonGroups(comparisonConfig, allGroups, sortMode) {
2239
2510
  const allGroupSummaries = /* @__PURE__ */ new Map();
2240
2511
  const withErrors = [];
2241
2512
  const onlyInLeft = [];
@@ -2245,7 +2516,9 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2245
2516
  function addSummaryForGroup(group, root, validInL, validInR) {
2246
2517
  let scores;
2247
2518
  if (validInL && validInR) {
2248
- scores = getScoresForTestSummaries(group.testSummaries, comparisonConfig.thresholds);
2519
+ const isRelativeMode = sortMode === "max-diff-relative" || sortMode === "avg-diff-relative";
2520
+ const thresholds = isRelativeMode ? comparisonConfig.ratioThresholds : comparisonConfig.thresholds;
2521
+ scores = getScoresForTestSummaries(group.testSummaries, thresholds, sortMode);
2249
2522
  }
2250
2523
  const groupSummary = {
2251
2524
  root,
@@ -2254,7 +2527,9 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2254
2527
  };
2255
2528
  allGroupSummaries.set(group.key, groupSummary);
2256
2529
  if (validInL && validInR) {
2257
- if (scores.totalDiffCount !== scores.diffCountByBucket[0]) {
2530
+ const noDiffCount = scores.diffCountByBucket[0];
2531
+ const skippedCount = scores.diffCountByBucket[5];
2532
+ if (scores.totalDiffCount !== noDiffCount + skippedCount) {
2258
2533
  withDiffs.push(groupSummary);
2259
2534
  } else {
2260
2535
  withoutDiffs.push(groupSummary);
@@ -2284,7 +2559,7 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2284
2559
  break;
2285
2560
  }
2286
2561
  default:
2287
- (0, import_assert_never8.assertNever)(group.kind);
2562
+ (0, import_assert_never7.assertNever)(group.kind);
2288
2563
  }
2289
2564
  }
2290
2565
  if (withDiffs.length > 1) {
@@ -2346,13 +2621,13 @@ function sortScenarioGroupSummaries(summaries) {
2346
2621
  });
2347
2622
  }
2348
2623
  function compareScores(a, b) {
2349
- if (a.totalMaxDiffByBucket.length !== b.totalMaxDiffByBucket.length) {
2624
+ if (a.totalDiffByBucket.length !== b.totalDiffByBucket.length) {
2350
2625
  return 0;
2351
2626
  }
2352
- const len = a.totalMaxDiffByBucket.length;
2627
+ const len = a.totalDiffByBucket.length;
2353
2628
  for (let i = len - 1; i >= 0; i--) {
2354
- const aTotal = a.totalMaxDiffByBucket[i];
2355
- const bTotal = b.totalMaxDiffByBucket[i];
2629
+ const aTotal = a.totalDiffByBucket[i];
2630
+ const bTotal = b.totalDiffByBucket[i];
2356
2631
  if (aTotal > bTotal) {
2357
2632
  return 1;
2358
2633
  } else if (aTotal < bTotal) {
@@ -2364,7 +2639,7 @@ function compareScores(a, b) {
2364
2639
 
2365
2640
  // src/comparison/config/parse/comparison-parser.ts
2366
2641
  var import_ajv2 = __toESM(require("ajv"), 1);
2367
- var import_assert_never9 = __toESM(require("assert-never"), 1);
2642
+ var import_assert_never8 = __toESM(require("assert-never"), 1);
2368
2643
  var import_neverthrow2 = require("neverthrow");
2369
2644
  var import_yaml2 = __toESM(require("yaml"), 1);
2370
2645
 
@@ -2900,7 +3175,7 @@ function parseComparisonSpecs(specSource) {
2900
3175
  parsed = import_yaml2.default.parse(specSource.content);
2901
3176
  break;
2902
3177
  default:
2903
- (0, import_assert_never9.default)(specSource.kind);
3178
+ (0, import_assert_never8.default)(specSource.kind);
2904
3179
  }
2905
3180
  if (validate(parsed)) {
2906
3181
  for (const specItem of parsed) {
@@ -3127,7 +3402,7 @@ function viewGroupSpecFromParsed(parsedViewGroup) {
3127
3402
  }
3128
3403
 
3129
3404
  // src/comparison/config/resolve/comparison-resolver.ts
3130
- var import_assert_never11 = require("assert-never");
3405
+ var import_assert_never10 = require("assert-never");
3131
3406
 
3132
3407
  // src/bundle/model-inputs.ts
3133
3408
  var ModelInputs = class {
@@ -3190,7 +3465,7 @@ var ModelInputs = class {
3190
3465
  };
3191
3466
 
3192
3467
  // src/comparison/config/resolve/comparison-scenario-specs.ts
3193
- var import_assert_never10 = require("assert-never");
3468
+ var import_assert_never9 = require("assert-never");
3194
3469
  function scenarioSpecsFromSettings(settings) {
3195
3470
  switch (settings.kind) {
3196
3471
  case "all-inputs-settings": {
@@ -3203,7 +3478,7 @@ function scenarioSpecsFromSettings(settings) {
3203
3478
  return [specL, specR];
3204
3479
  }
3205
3480
  default:
3206
- (0, import_assert_never10.assertNever)(settings);
3481
+ (0, import_assert_never9.assertNever)(settings);
3207
3482
  }
3208
3483
  }
3209
3484
  function scenarioSpecFromInputs(inputs, side) {
@@ -3422,7 +3697,7 @@ function resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genK
3422
3697
  ];
3423
3698
  }
3424
3699
  default:
3425
- (0, import_assert_never11.assertNever)(scenarioSpec);
3700
+ (0, import_assert_never10.assertNever)(scenarioSpec);
3426
3701
  }
3427
3702
  }
3428
3703
  function resolveScenarioMatrix(modelInputsL, modelInputsR, genKey) {
@@ -3478,7 +3753,7 @@ function resolveScenarioForInputSpecs(modelInputsL, modelInputsR, key, id, title
3478
3753
  case "input-at-value":
3479
3754
  return resolveInputForName(modelInputsL, modelInputsR, inputSpec.inputName, inputSpec.value);
3480
3755
  default:
3481
- (0, import_assert_never11.assertNever)(inputSpec);
3756
+ (0, import_assert_never10.assertNever)(inputSpec);
3482
3757
  }
3483
3758
  });
3484
3759
  const settings = {
@@ -3511,7 +3786,7 @@ function resolveScenarioForDistinctInputSpecs(modelInputsL, modelInputsR, key, i
3511
3786
  inputState = resolveInputForNameInModel(modelInputs, inputSpec.inputName, inputSpec.value);
3512
3787
  break;
3513
3788
  default:
3514
- (0, import_assert_never11.assertNever)(inputSpec);
3789
+ (0, import_assert_never10.assertNever)(inputSpec);
3515
3790
  }
3516
3791
  if (inputState.error !== void 0) {
3517
3792
  inputsWithErrors.push({
@@ -3727,7 +4002,7 @@ function inputValueAtPosition2(inputVar, position) {
3727
4002
  case "at-maximum":
3728
4003
  return inputVar.maxValue;
3729
4004
  default:
3730
- (0, import_assert_never11.assertNever)(position);
4005
+ (0, import_assert_never10.assertNever)(position);
3731
4006
  }
3732
4007
  }
3733
4008
  var ResolvedScenarioGroups = class {
@@ -3786,7 +4061,7 @@ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, grap
3786
4061
  return [...graphIds];
3787
4062
  }
3788
4063
  default:
3789
- (0, import_assert_never11.assertNever)(graphsSpec.preset);
4064
+ (0, import_assert_never10.assertNever)(graphsSpec.preset);
3790
4065
  }
3791
4066
  }
3792
4067
  // eslint-disable-next-line no-fallthrough
@@ -3800,7 +4075,7 @@ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, grap
3800
4075
  return groupSpec.graphIds;
3801
4076
  }
3802
4077
  default:
3803
- (0, import_assert_never11.assertNever)(graphsSpec);
4078
+ (0, import_assert_never10.assertNever)(graphsSpec);
3804
4079
  }
3805
4080
  }
3806
4081
  function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphIds, graphOrder) {
@@ -3974,7 +4249,7 @@ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolved
3974
4249
  views.push(resolveViewForScenario(void 0, void 0, scenario, graphIds, graphOrder));
3975
4250
  break;
3976
4251
  default:
3977
- (0, import_assert_never11.assertNever)(scenario);
4252
+ (0, import_assert_never10.assertNever)(scenario);
3978
4253
  }
3979
4254
  }
3980
4255
  } else {
@@ -3983,13 +4258,13 @@ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolved
3983
4258
  break;
3984
4259
  }
3985
4260
  default:
3986
- (0, import_assert_never11.assertNever)(refSpec);
4261
+ (0, import_assert_never10.assertNever)(refSpec);
3987
4262
  }
3988
4263
  }
3989
4264
  break;
3990
4265
  }
3991
4266
  default:
3992
- (0, import_assert_never11.assertNever)(viewGroupSpec);
4267
+ (0, import_assert_never10.assertNever)(viewGroupSpec);
3993
4268
  }
3994
4269
  return {
3995
4270
  kind: "view-group",
@@ -4052,9 +4327,9 @@ var ComparisonDatasetsImpl = class {
4052
4327
  }
4053
4328
  const allOutputVarKeysSet = /* @__PURE__ */ new Set();
4054
4329
  const modelOutputVarKeysSet = /* @__PURE__ */ new Set();
4055
- function addOutputVars(outputVars, handleRenames) {
4330
+ function addOutputVars(outputVars, handleRenames2) {
4056
4331
  outputVars.forEach((outputVar, key) => {
4057
- const remappedKey = handleRenames ? leftKeyForRightKey(key) : key;
4332
+ const remappedKey = handleRenames2 ? leftKeyForRightKey(key) : key;
4058
4333
  allOutputVarKeysSet.add(remappedKey);
4059
4334
  if (outputVar.sourceName === void 0) {
4060
4335
  modelOutputVarKeysSet.add(remappedKey);
@@ -4168,109 +4443,41 @@ var ComparisonScenariosImpl = class {
4168
4443
  }
4169
4444
  };
4170
4445
 
4171
- // src/config/synchronized-model.ts
4172
- function synchronizedBundleModel(sourceModel) {
4173
- const promiseQueue = new PromiseQueue();
4174
- return {
4175
- modelSpec: sourceModel.modelSpec,
4176
- getDatasetsForScenario: (scenarioSpec, datasetKeys) => {
4177
- return promiseQueue.add(() => sourceModel.getDatasetsForScenario(scenarioSpec, datasetKeys));
4178
- },
4179
- getGraphDataForScenario: (scenarioSpec, graphId) => {
4180
- return promiseQueue.add(() => sourceModel.getGraphDataForScenario(scenarioSpec, graphId));
4181
- },
4182
- getGraphLinksForScenario: sourceModel.getGraphLinksForScenario.bind(sourceModel)
4183
- };
4184
- }
4185
- var PromiseQueue = class {
4186
- constructor() {
4187
- this.tasks = [];
4188
- this.runningCount = 0;
4189
- }
4190
- add(f) {
4191
- return new Promise((resolve, reject) => {
4192
- const run = () => __async(this, null, function* () {
4193
- this.runningCount++;
4194
- const promise = f();
4195
- try {
4196
- const result = yield promise;
4197
- resolve(result);
4198
- } catch (e) {
4199
- reject(e);
4200
- } finally {
4201
- this.runningCount--;
4202
- this.runNext();
4203
- }
4204
- });
4205
- if (this.runningCount < 1) {
4206
- run();
4207
- } else {
4208
- this.tasks.push(run);
4209
- }
4210
- });
4211
- }
4212
- runNext() {
4213
- if (this.tasks.length > 0) {
4214
- const task = this.tasks.shift();
4215
- if (task) {
4216
- task();
4217
- }
4218
- }
4219
- }
4220
- };
4221
-
4222
4446
  // src/config/config.ts
4223
4447
  function createConfig(options) {
4224
4448
  return __async(this, null, function* () {
4225
- var _a;
4226
- const origCurrentBundle = yield loadSynchronized(options.current);
4449
+ var _a, _b, _c, _d, _e;
4450
+ let concurrentModels;
4451
+ if (options.concurrency === void 0) {
4452
+ concurrentModels = 1;
4453
+ } else if (options.concurrency === 0) {
4454
+ let coreCount;
4455
+ if (typeof navigator !== "undefined") {
4456
+ coreCount = navigator.hardwareConcurrency;
4457
+ }
4458
+ if (coreCount === void 0 || coreCount < 1) {
4459
+ coreCount = 1;
4460
+ }
4461
+ concurrentModels = Math.max(1, Math.floor(coreCount / 2));
4462
+ } else {
4463
+ concurrentModels = Math.max(1, options.concurrency);
4464
+ }
4465
+ const origCurrentBundle = yield loadBundle(options.current, concurrentModels);
4227
4466
  let currentBundle;
4228
4467
  let comparisonConfig;
4229
4468
  if (options.comparison === void 0) {
4230
4469
  currentBundle = origCurrentBundle;
4231
4470
  } else {
4232
- const baselineBundle = yield loadSynchronized(options.comparison.baseline);
4233
- const renamedDatasetKeys = (_a = options.comparison.datasets) == null ? void 0 : _a.renamedDatasetKeys;
4234
- const invertedRenamedKeys = /* @__PURE__ */ new Map();
4235
- renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
4236
- invertedRenamedKeys.set(newKey, oldKey);
4237
- });
4238
- const rightKeyForLeftKey = (leftKey) => {
4239
- return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
4240
- };
4241
- const leftKeyForRightKey = (rightKey) => {
4242
- return invertedRenamedKeys.get(rightKey) || rightKey;
4243
- };
4244
- const origBundleModelR = origCurrentBundle.model;
4245
- const adjBundleModelR = {
4246
- modelSpec: origBundleModelR.modelSpec,
4247
- getDatasetsForScenario: (scenarioSpec, datasetKeys) => __async(this, null, function* () {
4248
- const rightKeys = datasetKeys.map(rightKeyForLeftKey);
4249
- const result = yield origBundleModelR.getDatasetsForScenario(scenarioSpec, rightKeys);
4250
- const mapWithRightKeys = result.datasetMap;
4251
- const mapWithLeftKeys = /* @__PURE__ */ new Map();
4252
- for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
4253
- const leftKey = leftKeyForRightKey(rightKey);
4254
- mapWithLeftKeys.set(leftKey, dataset);
4255
- }
4256
- return {
4257
- datasetMap: mapWithLeftKeys,
4258
- modelRunTime: result.modelRunTime
4259
- };
4260
- }),
4261
- getGraphDataForScenario: origBundleModelR.getGraphDataForScenario.bind(origBundleModelR),
4262
- getGraphLinksForScenario: origBundleModelR.getGraphLinksForScenario.bind(origBundleModelR)
4263
- };
4264
- currentBundle = __spreadProps(__spreadValues({}, origCurrentBundle), {
4265
- model: adjBundleModelR
4266
- });
4267
- const modelSpecL = baselineBundle.model.modelSpec;
4268
- const modelSpecR = currentBundle.model.modelSpec;
4471
+ const baselineBundle = yield loadBundle(options.comparison.baseline, concurrentModels);
4472
+ currentBundle = handleRenames(origCurrentBundle, (_a = options.comparison.datasets) == null ? void 0 : _a.renamedDatasetKeys);
4473
+ const modelSpecL = baselineBundle.modelSpec;
4474
+ const modelSpecR = currentBundle.modelSpec;
4269
4475
  const comparisonDefs = resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, options.comparison.specs);
4270
4476
  comparisonConfig = {
4271
4477
  bundleL: baselineBundle,
4272
4478
  bundleR: currentBundle,
4273
- thresholds: options.comparison.thresholds,
4479
+ thresholds: (_b = options.comparison.thresholds) != null ? _b : [1, 5, 10],
4480
+ ratioThresholds: (_c = options.comparison.ratioThresholds) != null ? _c : [1, 2, 3],
4274
4481
  scenarios: getComparisonScenarios(comparisonDefs.scenarios),
4275
4482
  datasets: getComparisonDatasets(modelSpecL, modelSpecR, options.comparison.datasets),
4276
4483
  viewGroups: comparisonDefs.viewGroups,
@@ -4281,26 +4488,75 @@ function createConfig(options) {
4281
4488
  bundle: currentBundle,
4282
4489
  tests: options.check.tests
4283
4490
  };
4491
+ const executors = /* @__PURE__ */ new Map();
4492
+ for (let i = 0; i < checkConfig.bundle.models.length; i++) {
4493
+ const bundleModelL = (_d = comparisonConfig == null ? void 0 : comparisonConfig.bundleL.models) == null ? void 0 : _d[i];
4494
+ const bundleModelR = ((_e = comparisonConfig == null ? void 0 : comparisonConfig.bundleR.models) == null ? void 0 : _e[i]) || checkConfig.bundle.models[i];
4495
+ const executor = createExecutor(bundleModelL, bundleModelR);
4496
+ executors.set(`executor-${i}`, executor);
4497
+ }
4498
+ TaskQueue.initialize(executors);
4284
4499
  return {
4285
4500
  check: checkConfig,
4286
4501
  comparison: comparisonConfig
4287
4502
  };
4288
4503
  });
4289
4504
  }
4290
- function loadSynchronized(sourceBundle) {
4505
+ function loadBundle(bundle, concurrentModels) {
4291
4506
  return __async(this, null, function* () {
4292
- const sourceModel = yield sourceBundle.bundle.initModel();
4293
- const synchronizedModel = synchronizedBundleModel(sourceModel);
4507
+ const initCalls = Array.from({ length: concurrentModels }, () => bundle.bundle.initModel());
4508
+ const models = yield Promise.all(initCalls);
4294
4509
  return {
4295
- name: sourceBundle.name,
4296
- version: sourceBundle.bundle.version,
4297
- model: synchronizedModel
4510
+ name: bundle.name,
4511
+ version: bundle.bundle.version,
4512
+ modelSpec: bundle.bundle.modelSpec,
4513
+ models
4514
+ };
4515
+ });
4516
+ }
4517
+ function handleRenames(origCurrentBundle, renamedDatasetKeys) {
4518
+ if (renamedDatasetKeys === void 0 || renamedDatasetKeys.size === 0) {
4519
+ return origCurrentBundle;
4520
+ }
4521
+ const invertedRenamedKeys = /* @__PURE__ */ new Map();
4522
+ renamedDatasetKeys.forEach((newKey, oldKey) => {
4523
+ invertedRenamedKeys.set(newKey, oldKey);
4524
+ });
4525
+ const rightKeyForLeftKey = (leftKey) => {
4526
+ return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
4527
+ };
4528
+ const leftKeyForRightKey = (rightKey) => {
4529
+ return invertedRenamedKeys.get(rightKey) || rightKey;
4530
+ };
4531
+ function wrapModel(origBundleModelR) {
4532
+ return {
4533
+ modelSpec: origBundleModelR.modelSpec,
4534
+ getDatasetsForScenario: (scenarioSpec, datasetKeys) => __async(this, null, function* () {
4535
+ const rightKeys = datasetKeys.map(rightKeyForLeftKey);
4536
+ const result = yield origBundleModelR.getDatasetsForScenario(scenarioSpec, rightKeys);
4537
+ const mapWithRightKeys = result.datasetMap;
4538
+ const mapWithLeftKeys = /* @__PURE__ */ new Map();
4539
+ for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
4540
+ const leftKey = leftKeyForRightKey(rightKey);
4541
+ mapWithLeftKeys.set(leftKey, dataset);
4542
+ }
4543
+ return {
4544
+ datasetMap: mapWithLeftKeys,
4545
+ modelRunTime: result.modelRunTime
4546
+ };
4547
+ }),
4548
+ getGraphDataForScenario: origBundleModelR.getGraphDataForScenario.bind(origBundleModelR),
4549
+ getGraphLinksForScenario: origBundleModelR.getGraphLinksForScenario.bind(origBundleModelR)
4298
4550
  };
4551
+ }
4552
+ const wrappedModels = origCurrentBundle.models.map(wrapModel);
4553
+ return __spreadProps(__spreadValues({}, origCurrentBundle), {
4554
+ models: wrappedModels
4299
4555
  });
4300
4556
  }
4301
4557
 
4302
4558
  // src/perf/perf-runner.ts
4303
- var import_assert_never12 = require("assert-never");
4559
+ var import_assert_never11 = require("assert-never");
4304
4560
 
4305
4561
  // src/perf/perf-stats.ts
4306
4562
  var PerfStats = class {
@@ -4337,80 +4593,110 @@ var PerfStats = class {
4337
4593
  };
4338
4594
 
4339
4595
  // src/perf/perf-runner.ts
4340
- var warmupCount = 5;
4341
- var runCount = 100;
4596
+ function runPerfWithTaskQueue(taskQueue, callbacks, options) {
4597
+ const perfRunner = new PerfRunner(taskQueue, callbacks, options);
4598
+ perfRunner.start();
4599
+ return () => {
4600
+ perfRunner.cancel();
4601
+ };
4602
+ }
4603
+ function runPerf(callbacks, options) {
4604
+ const taskQueue = TaskQueue.getInstance();
4605
+ return runPerfWithTaskQueue(taskQueue, callbacks, options);
4606
+ }
4342
4607
  var PerfRunner = class {
4343
- constructor(bundleModelL, bundleModelR, mode = "serial") {
4344
- this.bundleModelL = bundleModelL;
4345
- this.bundleModelR = bundleModelR;
4346
- this.mode = mode;
4347
- const scenarioSpec = allInputsAtPositionSpec("at-default");
4348
- this.taskQueue = new TaskQueue({
4349
- process: (request) => __async(this, null, function* () {
4350
- switch (request.kind) {
4351
- case "left": {
4352
- const result = yield bundleModelL.getDatasetsForScenario(scenarioSpec, []);
4353
- return {
4354
- runTimeL: result.modelRunTime
4355
- };
4356
- }
4357
- case "right": {
4358
- const result = yield bundleModelR.getDatasetsForScenario(scenarioSpec, []);
4359
- return {
4360
- runTimeR: result.modelRunTime
4361
- };
4362
- }
4363
- case "both": {
4364
- const [resultL, resultR] = yield Promise.all([
4365
- bundleModelL.getDatasetsForScenario(scenarioSpec, []),
4366
- bundleModelR.getDatasetsForScenario(scenarioSpec, [])
4367
- ]);
4368
- return {
4369
- runTimeL: resultL.modelRunTime,
4370
- runTimeR: resultR.modelRunTime
4371
- };
4372
- }
4373
- default:
4374
- (0, import_assert_never12.assertNever)(request.kind);
4375
- }
4376
- })
4377
- });
4608
+ constructor(taskQueue, callbacks, options) {
4609
+ this.taskQueue = taskQueue;
4610
+ this.callbacks = callbacks;
4611
+ this.options = options;
4612
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4613
+ this.stopped = false;
4614
+ }
4615
+ cancel() {
4616
+ if (!this.stopped) {
4617
+ for (const taskKey of this.pendingTaskKeys) {
4618
+ this.taskQueue.cancelTask(taskKey);
4619
+ }
4620
+ this.stopped = true;
4621
+ }
4378
4622
  }
4379
4623
  start() {
4624
+ var _a, _b, _c, _d, _e, _f;
4380
4625
  const statsL = new PerfStats();
4381
4626
  const statsR = new PerfStats();
4382
- this.taskQueue.onIdle = (error) => {
4383
- var _a;
4384
- if (error) {
4385
- this.onError(error);
4386
- } else {
4387
- (_a = this.onComplete) == null ? void 0 : _a.call(this, statsL.toReport(), statsR.toReport());
4388
- }
4389
- };
4390
- const taskQueue = this.taskQueue;
4391
- function addTask(index, warmup, kind) {
4392
- const key = `${warmup ? "warmup-" : ""}${kind}-${index}`;
4393
- const request = {
4394
- kind
4395
- };
4396
- taskQueue.addTask(key, request, (response) => {
4397
- if (!warmup && response.runTimeL !== void 0) {
4398
- statsL.addRun(response.runTimeL);
4399
- }
4400
- if (!warmup && response.runTimeR !== void 0) {
4401
- statsR.addRun(response.runTimeR);
4402
- }
4403
- });
4627
+ const scenarioSpec = allInputsAtPositionSpec("at-default");
4628
+ const warmupCount = (_b = (_a = this.options) == null ? void 0 : _a.warmupCount) != null ? _b : 5;
4629
+ const runCount = (_d = (_c = this.options) == null ? void 0 : _c.runCount) != null ? _d : 100;
4630
+ let totalTasks = 0;
4631
+ if (((_e = this.options) == null ? void 0 : _e.mode) === "parallel") {
4632
+ totalTasks = warmupCount + runCount;
4633
+ } else {
4634
+ totalTasks = (warmupCount + runCount) * 2;
4404
4635
  }
4636
+ let tasksCompleted = 0;
4637
+ let perfTaskId = 1;
4638
+ const addTask = (warmup, kind) => {
4639
+ const task = {
4640
+ key: `perf-runner-${perfTaskId++}`,
4641
+ kind: "perf-runner",
4642
+ process: (bundleModels) => __async(this, null, function* () {
4643
+ var _a2, _b2, _c2, _d2;
4644
+ this.pendingTaskKeys.delete(task.key);
4645
+ try {
4646
+ let runTimeL;
4647
+ let runTimeR;
4648
+ switch (kind) {
4649
+ case "left": {
4650
+ const result = yield bundleModels.L.getDatasetsForScenario(scenarioSpec, []);
4651
+ runTimeL = result.modelRunTime;
4652
+ break;
4653
+ }
4654
+ case "right": {
4655
+ const result = yield bundleModels.R.getDatasetsForScenario(scenarioSpec, []);
4656
+ runTimeR = result.modelRunTime;
4657
+ break;
4658
+ }
4659
+ case "both": {
4660
+ const [resultL, resultR] = yield Promise.all([
4661
+ bundleModels.L.getDatasetsForScenario(scenarioSpec, []),
4662
+ bundleModels.R.getDatasetsForScenario(scenarioSpec, [])
4663
+ ]);
4664
+ runTimeL = resultL.modelRunTime;
4665
+ runTimeR = resultR.modelRunTime;
4666
+ break;
4667
+ }
4668
+ default:
4669
+ (0, import_assert_never11.assertNever)(kind);
4670
+ }
4671
+ if (!warmup) {
4672
+ if (runTimeL !== void 0) {
4673
+ statsL.addRun(runTimeL);
4674
+ }
4675
+ if (runTimeR !== void 0) {
4676
+ statsR.addRun(runTimeR);
4677
+ }
4678
+ }
4679
+ tasksCompleted++;
4680
+ if (tasksCompleted === totalTasks) {
4681
+ (_b2 = (_a2 = this.callbacks).onComplete) == null ? void 0 : _b2.call(_a2, statsL.toReport(), statsR.toReport());
4682
+ }
4683
+ } catch (error) {
4684
+ (_d2 = (_c2 = this.callbacks).onError) == null ? void 0 : _d2.call(_c2, error);
4685
+ }
4686
+ })
4687
+ };
4688
+ this.taskQueue.addTask(task);
4689
+ this.pendingTaskKeys.add(task.key);
4690
+ };
4405
4691
  function addTasks(kind) {
4406
4692
  for (let i = 0; i < warmupCount; i++) {
4407
- addTask(i, true, kind);
4693
+ addTask(true, kind);
4408
4694
  }
4409
4695
  for (let i = 0; i < runCount; i++) {
4410
- addTask(i, false, kind);
4696
+ addTask(false, kind);
4411
4697
  }
4412
4698
  }
4413
- if (this.mode === "parallel") {
4699
+ if (((_f = this.options) == null ? void 0 : _f.mode) === "parallel") {
4414
4700
  addTasks("both");
4415
4701
  } else {
4416
4702
  addTasks("left");
@@ -4419,6 +4705,303 @@ var PerfRunner = class {
4419
4705
  }
4420
4706
  };
4421
4707
 
4708
+ // src/trace/trace-runner.ts
4709
+ var import_assert_never12 = require("assert-never");
4710
+ function runTraceWithTaskQueue(modelSpec, taskQueue, callbacks, options) {
4711
+ const traceRunner = new TraceRunner(taskQueue, callbacks);
4712
+ traceRunner.start(modelSpec, options);
4713
+ return () => {
4714
+ traceRunner.cancel();
4715
+ };
4716
+ }
4717
+ function runTrace(modelSpec, callbacks, options) {
4718
+ const taskQueue = TaskQueue.getInstance();
4719
+ return runTraceWithTaskQueue(modelSpec, taskQueue, callbacks, options);
4720
+ }
4721
+ var TraceRunner = class {
4722
+ constructor(taskQueue, callbacks) {
4723
+ this.taskQueue = taskQueue;
4724
+ this.callbacks = callbacks;
4725
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4726
+ this.stopped = false;
4727
+ }
4728
+ cancel() {
4729
+ if (!this.stopped) {
4730
+ for (const taskKey of this.pendingTaskKeys) {
4731
+ this.taskQueue.cancelTask(taskKey);
4732
+ }
4733
+ this.stopped = true;
4734
+ }
4735
+ }
4736
+ start(modelSpec, options) {
4737
+ const allDatasetKeys = [...modelSpec.implVars.keys()];
4738
+ const traceRequests = [];
4739
+ const batchSize = 2e3;
4740
+ for (let i = 0; i < allDatasetKeys.length; i += batchSize) {
4741
+ const datasetKeysForBatch = allDatasetKeys.slice(i, i + batchSize);
4742
+ switch (options.kind) {
4743
+ case "compare-to-bundle":
4744
+ traceRequests.push({
4745
+ kind: "compare-to-bundle",
4746
+ datasetKeys: datasetKeysForBatch,
4747
+ bundleSide0: options.bundleSide0,
4748
+ scenarioSpec0: options.scenarioSpec0,
4749
+ bundleSide1: options.bundleSide1,
4750
+ scenarioSpec1: options.scenarioSpec1
4751
+ });
4752
+ break;
4753
+ case "compare-to-ext-data":
4754
+ traceRequests.push({
4755
+ kind: "compare-to-ext-data",
4756
+ datasetKeys: datasetKeysForBatch,
4757
+ extData: options.extData,
4758
+ bundleSide: options.bundleSide,
4759
+ scenarioSpec: options.scenarioSpec
4760
+ });
4761
+ break;
4762
+ default:
4763
+ (0, import_assert_never12.assertNever)(options);
4764
+ }
4765
+ }
4766
+ const allDatasetReports = /* @__PURE__ */ new Map();
4767
+ const taskCount = traceRequests.length;
4768
+ let tasksCompleted = 0;
4769
+ let traceTaskId = 1;
4770
+ for (const traceRequest of traceRequests) {
4771
+ const task = {
4772
+ key: `trace-runner-${traceTaskId++}`,
4773
+ kind: "trace-runner",
4774
+ process: (bundleModels) => __async(this, null, function* () {
4775
+ var _a, _b;
4776
+ this.pendingTaskKeys.delete(task.key);
4777
+ let datasetReports;
4778
+ switch (traceRequest.kind) {
4779
+ case "compare-to-bundle":
4780
+ datasetReports = yield processCompareToBundleRequest(traceRequest, bundleModels);
4781
+ break;
4782
+ case "compare-to-ext-data":
4783
+ datasetReports = yield processCompareToExtDataRequest(traceRequest, bundleModels);
4784
+ break;
4785
+ default:
4786
+ (0, import_assert_never12.assertNever)(traceRequest);
4787
+ }
4788
+ for (const datasetReport of datasetReports) {
4789
+ allDatasetReports.set(datasetReport.datasetKey, datasetReport);
4790
+ }
4791
+ tasksCompleted++;
4792
+ if (tasksCompleted === taskCount) {
4793
+ const traceReport = {
4794
+ datasetReports: allDatasetReports
4795
+ };
4796
+ (_b = (_a = this.callbacks).onComplete) == null ? void 0 : _b.call(_a, traceReport);
4797
+ }
4798
+ })
4799
+ };
4800
+ this.taskQueue.addTask(task);
4801
+ this.pendingTaskKeys.add(task.key);
4802
+ }
4803
+ }
4804
+ };
4805
+ function processCompareToBundleRequest(request, bundleModels) {
4806
+ return __async(this, null, function* () {
4807
+ const bundleModel0 = request.bundleSide0 === "left" ? bundleModels.L : bundleModels.R;
4808
+ const bundleModel1 = request.bundleSide1 === "left" ? bundleModels.L : bundleModels.R;
4809
+ let result0;
4810
+ let result1;
4811
+ if (bundleModel1 === bundleModel0) {
4812
+ result0 = yield bundleModel0.getDatasetsForScenario(request.scenarioSpec0, request.datasetKeys);
4813
+ result1 = yield bundleModel1.getDatasetsForScenario(request.scenarioSpec1, request.datasetKeys);
4814
+ } else {
4815
+ ;
4816
+ [result0, result1] = yield Promise.all([
4817
+ bundleModel0.getDatasetsForScenario(request.scenarioSpec0, request.datasetKeys),
4818
+ bundleModel1.getDatasetsForScenario(request.scenarioSpec1, request.datasetKeys)
4819
+ ]);
4820
+ }
4821
+ const datasetReports = [];
4822
+ for (const datasetKey of request.datasetKeys) {
4823
+ const dataset0 = result0.datasetMap.get(datasetKey);
4824
+ const dataset1 = result1.datasetMap.get(datasetKey);
4825
+ const datasetReport = diffDatasets2(
4826
+ datasetKey,
4827
+ dataset0,
4828
+ dataset1,
4829
+ /*matchPrecisionOfLeft=*/
4830
+ false
4831
+ );
4832
+ datasetReports.push(datasetReport);
4833
+ }
4834
+ return datasetReports;
4835
+ });
4836
+ }
4837
+ function processCompareToExtDataRequest(request, bundleModels) {
4838
+ return __async(this, null, function* () {
4839
+ const bundleModel = request.bundleSide === "left" ? bundleModels.L : bundleModels.R;
4840
+ const resultR = yield bundleModel.getDatasetsForScenario(request.scenarioSpec, request.datasetKeys);
4841
+ const datasetReports = [];
4842
+ for (const datasetKey of request.datasetKeys) {
4843
+ let datasetL = request.extData.get(datasetKey);
4844
+ if (datasetL === void 0) {
4845
+ const datasetKeyParts = datasetKey.split("[");
4846
+ if (datasetKeyParts.length === 2) {
4847
+ const baseKey = datasetKeyParts[0];
4848
+ const keySubParts = datasetKeyParts[1].replace("]", "");
4849
+ const keySubIds = keySubParts.split(",");
4850
+ const subIdPermutations = permutationsOf(keySubIds);
4851
+ for (const subIds of subIdPermutations) {
4852
+ const datDatasetKey = `${baseKey}[${subIds.join(",")}]`;
4853
+ datasetL = request.extData.get(datDatasetKey);
4854
+ if (datasetL !== void 0) {
4855
+ break;
4856
+ }
4857
+ }
4858
+ }
4859
+ if (datasetL === void 0) {
4860
+ console.warn(`WARNING: Failed to find data in dat file for key=${datasetKey}`);
4861
+ }
4862
+ }
4863
+ const datasetR = resultR.datasetMap.get(datasetKey);
4864
+ const datasetReport = diffDatasets2(
4865
+ datasetKey,
4866
+ datasetL,
4867
+ datasetR,
4868
+ /*matchPrecisionOfLeft=*/
4869
+ true
4870
+ );
4871
+ datasetReports.push(datasetReport);
4872
+ }
4873
+ return datasetReports;
4874
+ });
4875
+ }
4876
+ function diffDatasets2(datasetKey, datasetL, datasetR, matchPrecisionOfLeft) {
4877
+ const points = /* @__PURE__ */ new Map();
4878
+ let minValueL = Number.MAX_VALUE;
4879
+ let maxValueL = Number.MIN_VALUE;
4880
+ let minValueR = Number.MAX_VALUE;
4881
+ let maxValueR = Number.MIN_VALUE;
4882
+ let minValue = Number.MAX_VALUE;
4883
+ let maxValue = Number.MIN_VALUE;
4884
+ let minRawDiff = Number.MAX_VALUE;
4885
+ let maxRawDiff = -1;
4886
+ let maxDiffPoint;
4887
+ let diffCount = 0;
4888
+ let totalRawDiff = 0;
4889
+ if (datasetL && datasetR) {
4890
+ const times = /* @__PURE__ */ new Set([...datasetL.keys(), ...datasetR.keys()]);
4891
+ for (const t of times) {
4892
+ const valueL = datasetL.get(t);
4893
+ if (valueL !== void 0) {
4894
+ if (valueL < minValueL) minValueL = valueL;
4895
+ if (valueL > maxValueL) maxValueL = valueL;
4896
+ if (valueL < minValue) minValue = valueL;
4897
+ if (valueL > maxValue) maxValue = valueL;
4898
+ }
4899
+ let valueR;
4900
+ const rawValueR = datasetR.get(t);
4901
+ if (rawValueR !== void 0) {
4902
+ if (matchPrecisionOfLeft && valueL !== void 0) {
4903
+ valueR = matchPrecision(rawValueR, valueL);
4904
+ } else {
4905
+ valueR = rawValueR;
4906
+ }
4907
+ if (valueR < minValueR) minValueR = valueR;
4908
+ if (valueR > maxValueR) maxValueR = valueR;
4909
+ if (valueR < minValue) minValue = valueR;
4910
+ if (valueR > maxValue) maxValue = valueR;
4911
+ }
4912
+ if (valueL === void 0 || valueR === void 0) {
4913
+ continue;
4914
+ }
4915
+ const point = {
4916
+ time: t,
4917
+ valueL,
4918
+ valueR
4919
+ };
4920
+ points.set(t, point);
4921
+ const rawDiff = Math.abs(valueR - valueL);
4922
+ if (rawDiff < minRawDiff) {
4923
+ minRawDiff = rawDiff;
4924
+ }
4925
+ if (rawDiff > maxRawDiff) {
4926
+ maxRawDiff = rawDiff;
4927
+ maxDiffPoint = point;
4928
+ }
4929
+ diffCount++;
4930
+ totalRawDiff += rawDiff;
4931
+ }
4932
+ }
4933
+ function pct(x) {
4934
+ return x * 100;
4935
+ }
4936
+ let minDiff;
4937
+ let maxDiff;
4938
+ let avgDiff;
4939
+ if (minValueL === maxValueL && minValueR === maxValueR) {
4940
+ const diff = pct(maxValueL !== 0 ? Math.abs((maxValueR - maxValueL) / maxValueL) : 1);
4941
+ minDiff = diff;
4942
+ maxDiff = diff;
4943
+ avgDiff = diff;
4944
+ } else {
4945
+ const spread = maxValue - minValue;
4946
+ minDiff = pct(spread > 0 ? minRawDiff / spread : 0);
4947
+ maxDiff = pct(spread > 0 ? maxRawDiff / spread : 0);
4948
+ const avgRawDiff = totalRawDiff / diffCount;
4949
+ avgDiff = pct(spread > 0 ? avgRawDiff / spread : 0);
4950
+ }
4951
+ let validity;
4952
+ if (datasetL && datasetR) {
4953
+ validity = "both";
4954
+ } else if (datasetL) {
4955
+ validity = "left-only";
4956
+ } else if (datasetR) {
4957
+ validity = "right-only";
4958
+ } else {
4959
+ validity = "neither";
4960
+ }
4961
+ return {
4962
+ datasetKey,
4963
+ validity,
4964
+ points,
4965
+ minValue,
4966
+ maxValue,
4967
+ avgDiff,
4968
+ minDiff,
4969
+ maxDiff,
4970
+ maxDiffPoint
4971
+ };
4972
+ }
4973
+ function matchPrecision(x, baseline) {
4974
+ const s = baseline.toString();
4975
+ if (s.includes("e")) {
4976
+ return x;
4977
+ }
4978
+ const parts = s.split(".");
4979
+ if (parts.length < 2) {
4980
+ return x;
4981
+ }
4982
+ const sigDigits = parts[1].replace(/0+$/, "").length;
4983
+ if (sigDigits > 21) {
4984
+ return x;
4985
+ }
4986
+ return parseFloat(x.toFixed(sigDigits));
4987
+ }
4988
+ function permutationsOf(inputArr) {
4989
+ const result = [];
4990
+ const permute = (arr, m = []) => {
4991
+ if (arr.length === 0) {
4992
+ result.push(m);
4993
+ } else {
4994
+ for (let i = 0; i < arr.length; i++) {
4995
+ const curr = arr.slice();
4996
+ const next = curr.splice(i, 1);
4997
+ permute(curr.slice(), m.concat(next));
4998
+ }
4999
+ }
5000
+ };
5001
+ permute(inputArr);
5002
+ return result;
5003
+ }
5004
+
4422
5005
  // src/data/data-planner.ts
4423
5006
  var DataPlanner = class {
4424
5007
  /**
@@ -4605,10 +5188,10 @@ function scenarioPairUid(scenarioSpecL, scenarioSpecR) {
4605
5188
  }
4606
5189
 
4607
5190
  // src/check/check-runner.ts
4608
- function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios) {
4609
- const modelSpec = checkConfig.bundle.model.modelSpec;
5191
+ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, skipChecks) {
5192
+ const modelSpec = checkConfig.bundle.modelSpec;
4610
5193
  const checkPlanner = new CheckPlanner(modelSpec);
4611
- checkPlanner.addAllChecks(checkSpec, simplifyScenarios);
5194
+ checkPlanner.addAllChecks(checkSpec, skipChecks);
4612
5195
  const checkPlan = checkPlanner.buildPlan();
4613
5196
  const refDatasets = /* @__PURE__ */ new Map();
4614
5197
  for (const [dataRefKey, dataRef] of checkPlan.dataRefs.entries()) {
@@ -4621,11 +5204,15 @@ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplify
4621
5204
  }
4622
5205
  const checkResults = /* @__PURE__ */ new Map();
4623
5206
  for (const [checkKey, checkTask] of checkPlan.tasks.entries()) {
4624
- dataPlanner.addRequest(void 0, checkTask.scenario.spec, checkTask.dataset.datasetKey, (datasets) => {
4625
- const dataset = datasets.datasetR;
4626
- const checkResult = runCheck(checkTask, dataset, refDatasets);
4627
- checkResults.set(checkKey, checkResult);
4628
- });
5207
+ if (checkTask.skip === true) {
5208
+ checkResults.set(checkKey, { status: "skipped" });
5209
+ } else {
5210
+ dataPlanner.addRequest(void 0, checkTask.scenario.spec, checkTask.dataset.datasetKey, (datasets) => {
5211
+ const dataset = datasets.datasetR;
5212
+ const checkResult = runCheck(checkTask, dataset, refDatasets);
5213
+ checkResults.set(checkKey, checkResult);
5214
+ });
5215
+ }
4629
5216
  }
4630
5217
  return () => {
4631
5218
  return buildCheckReport(checkPlan, checkResults);
@@ -4690,21 +5277,73 @@ function runCheck(checkTask, dataset, refDatasets) {
4690
5277
  }
4691
5278
 
4692
5279
  // src/comparison/run/comparison-runner.ts
4693
- function runComparisons(comparisonConfig, dataPlanner) {
5280
+ function runComparisons(comparisonConfig, dataPlanner, skipScenarios) {
5281
+ function skipScenarioKey(title, subtitle) {
5282
+ let key = title.toLowerCase();
5283
+ if (subtitle) {
5284
+ key += ` :: ${subtitle.toLowerCase()}`;
5285
+ }
5286
+ return key;
5287
+ }
5288
+ const skipScenariosSet = new Set(skipScenarios.map((scenario) => skipScenarioKey(scenario.title, scenario.subtitle)));
5289
+ const allScenarios = [...comparisonConfig.scenarios.getAllScenarios()];
5290
+ let baselineScenario;
5291
+ const baselineScenarioIndex = allScenarios.findIndex((scenario) => {
5292
+ const settings = scenario.settings;
5293
+ return settings.kind === "all-inputs-settings" && settings.position === "at-default";
5294
+ });
5295
+ if (baselineScenarioIndex !== -1) {
5296
+ baselineScenario = allScenarios.splice(baselineScenarioIndex, 1)[0];
5297
+ }
4694
5298
  const testReports = [];
4695
- for (const scenario of comparisonConfig.scenarios.getAllScenarios()) {
5299
+ const baselineDiffReports = /* @__PURE__ */ new Map();
5300
+ function runComparisonsForScenario(scenario, isBaseline) {
4696
5301
  const datasetKeys = comparisonConfig.datasets.getDatasetKeysForScenario(scenario);
4697
- for (const datasetKey of datasetKeys) {
4698
- dataPlanner.addRequest(scenario.specL, scenario.specR, datasetKey, (datasets) => {
4699
- const diffReport = diffDatasets(datasets.datasetL, datasets.datasetR);
5302
+ const shouldSkip = skipScenariosSet.has(skipScenarioKey(scenario.title, scenario.subtitle));
5303
+ if (shouldSkip) {
5304
+ for (const datasetKey of datasetKeys) {
4700
5305
  testReports.push({
4701
5306
  scenarioKey: scenario.key,
4702
5307
  datasetKey,
4703
- diffReport
5308
+ diffReport: void 0
4704
5309
  });
5310
+ }
5311
+ return;
5312
+ }
5313
+ for (const datasetKey of datasetKeys) {
5314
+ dataPlanner.addRequest(scenario.specL, scenario.specR, datasetKey, (datasets) => {
5315
+ const diffReport = diffDatasets(datasets.datasetL, datasets.datasetR);
5316
+ if (isBaseline) {
5317
+ baselineDiffReports.set(datasetKey, diffReport);
5318
+ testReports.push({
5319
+ scenarioKey: scenario.key,
5320
+ datasetKey,
5321
+ diffReport
5322
+ });
5323
+ } else {
5324
+ const baselineDiffReport = baselineDiffReports.get(datasetKey);
5325
+ testReports.push({
5326
+ scenarioKey: scenario.key,
5327
+ datasetKey,
5328
+ diffReport,
5329
+ baselineDiffReport
5330
+ });
5331
+ }
4705
5332
  });
4706
5333
  }
4707
5334
  }
5335
+ runComparisonsForScenario(
5336
+ baselineScenario,
5337
+ /*isBaseline=*/
5338
+ true
5339
+ );
5340
+ for (const scenario of allScenarios) {
5341
+ runComparisonsForScenario(
5342
+ scenario,
5343
+ /*isBaseline=*/
5344
+ false
5345
+ );
5346
+ }
4708
5347
  return () => {
4709
5348
  return testReports;
4710
5349
  };
@@ -4712,74 +5351,52 @@ function runComparisons(comparisonConfig, dataPlanner) {
4712
5351
 
4713
5352
  // src/suite/suite-runner.ts
4714
5353
  var SuiteRunner = class {
4715
- constructor(config, callbacks) {
5354
+ constructor(config, taskQueue, callbacks) {
4716
5355
  this.config = config;
5356
+ this.taskQueue = taskQueue;
4717
5357
  this.callbacks = callbacks;
4718
5358
  this.perfStatsL = new PerfStats();
4719
5359
  this.perfStatsR = new PerfStats();
5360
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4720
5361
  this.stopped = false;
4721
- this.taskQueue = new TaskQueue({
4722
- process: (request) => {
4723
- return this.processRequest(request);
4724
- }
4725
- });
4726
5362
  }
4727
5363
  cancel() {
4728
5364
  if (!this.stopped) {
5365
+ for (const taskKey of this.pendingTaskKeys) {
5366
+ this.taskQueue.cancelTask(taskKey);
5367
+ }
4729
5368
  this.stopped = true;
4730
- this.taskQueue.shutdown();
4731
5369
  }
4732
5370
  }
4733
5371
  start(options) {
4734
5372
  var _a, _b, _c, _d, _e, _f, _g, _h;
4735
5373
  (_b = (_a = this.callbacks).onProgress) == null ? void 0 : _b.call(_a, 0);
4736
- const modelSpec = this.config.check.bundle.model.modelSpec;
4737
- const dataPlanner = new DataPlanner(modelSpec.outputVars.size);
4738
- const refDataPlanner = new DataPlanner(modelSpec.outputVars.size);
5374
+ const modelSpecR = this.config.check.bundle.modelSpec;
5375
+ const dataPlanner = new DataPlanner(modelSpecR.outputVars.size);
5376
+ const refDataPlanner = new DataPlanner(modelSpecR.outputVars.size);
4739
5377
  const checkSpecResult = parseTestYaml(this.config.check.tests);
4740
5378
  if (checkSpecResult.isErr()) {
4741
5379
  (_d = (_c = this.callbacks).onError) == null ? void 0 : _d.call(_c, checkSpecResult.error);
4742
5380
  return;
4743
5381
  }
4744
5382
  const checkSpec = checkSpecResult.value;
4745
- const simplifyScenarios = (options == null ? void 0 : options.simplifyScenarios) === true;
4746
- const buildCheckReport2 = runChecks(this.config.check, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios);
5383
+ const skipChecks = (options == null ? void 0 : options.skipChecks) || [];
5384
+ const buildCheckReport2 = runChecks(this.config.check, checkSpec, dataPlanner, refDataPlanner, skipChecks);
4747
5385
  let buildComparisonTestReports;
4748
5386
  if (this.config.comparison) {
4749
- buildComparisonTestReports = runComparisons(this.config.comparison, dataPlanner);
5387
+ const skipScenarios = (options == null ? void 0 : options.skipComparisonScenarios) || [];
5388
+ buildComparisonTestReports = runComparisons(this.config.comparison, dataPlanner, skipScenarios);
4750
5389
  }
4751
- this.taskQueue.onIdle = (error) => {
4752
- var _a2, _b2, _c2, _d2;
4753
- if (this.stopped) {
4754
- return;
4755
- }
4756
- if (error) {
4757
- (_b2 = (_a2 = this.callbacks).onError) == null ? void 0 : _b2.call(_a2, error);
4758
- } else {
4759
- const checkReport = buildCheckReport2();
4760
- let comparisonReport;
4761
- if (this.config.comparison) {
4762
- comparisonReport = {
4763
- testReports: buildComparisonTestReports(),
4764
- perfReportL: this.perfStatsL.toReport(),
4765
- perfReportR: this.perfStatsR.toReport()
4766
- };
4767
- }
4768
- (_d2 = (_c2 = this.callbacks).onComplete) == null ? void 0 : _d2.call(_c2, {
4769
- checkReport,
4770
- comparisonReport
4771
- });
4772
- }
4773
- };
4774
5390
  const refDataPlan = refDataPlanner.buildPlan();
4775
5391
  const dataPlan = dataPlanner.buildPlan();
4776
5392
  const dataRequests = [...refDataPlan.requests, ...dataPlan.requests];
4777
5393
  const taskCount = dataRequests.length;
4778
5394
  if (taskCount === 0) {
5395
+ const checkReport = buildCheckReport2();
4779
5396
  let comparisonReport;
4780
5397
  if (this.config.comparison) {
4781
5398
  comparisonReport = {
4782
- testReports: [],
5399
+ testReports: buildComparisonTestReports(),
4783
5400
  perfReportL: this.perfStatsL.toReport(),
4784
5401
  perfReportR: this.perfStatsR.toReport()
4785
5402
  };
@@ -4787,26 +5404,54 @@ var SuiteRunner = class {
4787
5404
  this.cancel();
4788
5405
  (_f = (_e = this.callbacks).onProgress) == null ? void 0 : _f.call(_e, 1);
4789
5406
  (_h = (_g = this.callbacks).onComplete) == null ? void 0 : _h.call(_g, {
4790
- checkReport: {
4791
- groups: []
4792
- },
5407
+ checkReport,
4793
5408
  comparisonReport
4794
5409
  });
4795
5410
  return;
4796
5411
  }
5412
+ const buildReport = (error) => {
5413
+ var _a2, _b2, _c2, _d2;
5414
+ if (error) {
5415
+ (_b2 = (_a2 = this.callbacks).onError) == null ? void 0 : _b2.call(_a2, error);
5416
+ } else {
5417
+ const checkReport = buildCheckReport2();
5418
+ let comparisonReport;
5419
+ if (this.config.comparison) {
5420
+ comparisonReport = {
5421
+ testReports: buildComparisonTestReports(),
5422
+ perfReportL: this.perfStatsL.toReport(),
5423
+ perfReportR: this.perfStatsR.toReport()
5424
+ };
5425
+ }
5426
+ (_d2 = (_c2 = this.callbacks).onComplete) == null ? void 0 : _d2.call(_c2, {
5427
+ checkReport,
5428
+ comparisonReport
5429
+ });
5430
+ }
5431
+ };
4797
5432
  let tasksCompleted = 0;
4798
5433
  let dataTaskId = 1;
4799
5434
  for (const dataRequest of dataRequests) {
4800
- this.taskQueue.addTask(`data${dataTaskId++}`, dataRequest, () => {
4801
- var _a2, _b2;
4802
- tasksCompleted++;
4803
- (_b2 = (_a2 = this.callbacks).onProgress) == null ? void 0 : _b2.call(_a2, tasksCompleted / taskCount);
4804
- });
5435
+ const task = {
5436
+ key: `suite-runner-${dataTaskId++}`,
5437
+ kind: "suite-runner",
5438
+ process: (bundleModels) => __async(this, null, function* () {
5439
+ var _a2, _b2;
5440
+ this.pendingTaskKeys.delete(task.key);
5441
+ yield this.processRequest(dataRequest, bundleModels);
5442
+ tasksCompleted++;
5443
+ (_b2 = (_a2 = this.callbacks).onProgress) == null ? void 0 : _b2.call(_a2, tasksCompleted / taskCount);
5444
+ if (tasksCompleted === taskCount) {
5445
+ buildReport();
5446
+ }
5447
+ })
5448
+ };
5449
+ this.taskQueue.addTask(task);
5450
+ this.pendingTaskKeys.add(task.key);
4805
5451
  }
4806
5452
  }
4807
- processRequest(request) {
5453
+ processRequest(request, bundleModels) {
4808
5454
  return __async(this, null, function* () {
4809
- var _a, _b;
4810
5455
  const datasetKeySet = /* @__PURE__ */ new Set();
4811
5456
  for (const dataTask of request.dataTasks) {
4812
5457
  datasetKeySet.add(dataTask.datasetKey);
@@ -4821,8 +5466,8 @@ var SuiteRunner = class {
4821
5466
  }
4822
5467
  });
4823
5468
  }
4824
- const bundleModelL = (_a = this.config.comparison) == null ? void 0 : _a.bundleL.model;
4825
- const bundleModelR = ((_b = this.config.comparison) == null ? void 0 : _b.bundleR.model) || this.config.check.bundle.model;
5469
+ const bundleModelL = bundleModels.L;
5470
+ const bundleModelR = bundleModels.R;
4826
5471
  const [datasetsResultL, datasetsResultR] = yield Promise.all([
4827
5472
  getDatasets(bundleModelL, request.scenarioSpecL),
4828
5473
  getDatasets(bundleModelR, request.scenarioSpecR)
@@ -4846,22 +5491,29 @@ var SuiteRunner = class {
4846
5491
  });
4847
5492
  }
4848
5493
  };
4849
- function runSuite(config, callbacks, options) {
4850
- const suiteRunner = new SuiteRunner(config, callbacks);
5494
+ function runSuiteWithTaskQueue(config, taskQueue, callbacks, options) {
5495
+ const suiteRunner = new SuiteRunner(config, taskQueue, callbacks);
4851
5496
  suiteRunner.start(options);
4852
5497
  return () => {
4853
5498
  suiteRunner.cancel();
4854
5499
  };
4855
5500
  }
5501
+ function runSuite(config, callbacks, options) {
5502
+ const taskQueue = TaskQueue.getInstance();
5503
+ return runSuiteWithTaskQueue(config, taskQueue, callbacks, options);
5504
+ }
4856
5505
 
4857
5506
  // src/suite/suite-reporting.ts
4858
- function suiteSummaryFromReport(suiteReport) {
5507
+ function suiteSummaryFromReport(suiteReport, elapsedMillis) {
4859
5508
  const checkSummary = checkSummaryFromReport(suiteReport.checkReport);
4860
5509
  let comparisonSummary;
4861
5510
  if (suiteReport.comparisonReport) {
4862
5511
  comparisonSummary = comparisonSummaryFromReport(suiteReport.comparisonReport);
4863
5512
  }
5513
+ const date = (/* @__PURE__ */ new Date()).toISOString();
4864
5514
  return {
5515
+ date,
5516
+ elapsed: elapsedMillis,
4865
5517
  checkSummary,
4866
5518
  comparisonSummary
4867
5519
  };
@@ -4870,20 +5522,27 @@ function suiteSummaryFromReport(suiteReport) {
4870
5522
  0 && (module.exports = {
4871
5523
  CheckDataCoordinator,
4872
5524
  ComparisonDataCoordinator,
4873
- PerfRunner,
4874
5525
  PerfStats,
4875
5526
  categorizeComparisonTestSummaries,
4876
5527
  checkReportFromSummary,
4877
5528
  checkSummaryFromReport,
4878
5529
  comparisonSummaryFromReport,
5530
+ createCheckDataCoordinator,
5531
+ createCheckDataCoordinatorForTests,
5532
+ createComparisonDataCoordinator,
4879
5533
  createConfig,
4880
5534
  datasetMessage,
5535
+ decodeImplVars,
4881
5536
  diffDatasets,
4882
5537
  diffGraphs,
5538
+ encodeImplVars,
4883
5539
  getScoresForTestSummaries,
4884
5540
  predicateMessage,
5541
+ runPerf,
4885
5542
  runSuite,
5543
+ runTrace,
4886
5544
  scenarioMessage,
4887
- suiteSummaryFromReport
5545
+ suiteSummaryFromReport,
5546
+ testSummaryFromReport
4888
5547
  });
4889
5548
  //# sourceMappingURL=index.cjs.map