@sdeverywhere/check-core 0.1.5 → 0.1.7

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.js CHANGED
@@ -38,33 +38,178 @@ var __async = (__this, __arguments, generator) => {
38
38
  });
39
39
  };
40
40
 
41
+ // src/bundle/impl-vars-codec.ts
42
+ function encodeImplVars(input) {
43
+ const subscripts = [];
44
+ const variables = [];
45
+ const varTypes = [];
46
+ const varInstances = {};
47
+ const subscriptMap = /* @__PURE__ */ new Map();
48
+ const variableMap = /* @__PURE__ */ new Map();
49
+ const varTypeMap = /* @__PURE__ */ new Map();
50
+ for (const [groupKey, implVars] of Object.entries(input)) {
51
+ const instances = [];
52
+ for (const implVar of implVars) {
53
+ const varIdInfo = parseSubscripts(implVar.varId);
54
+ const varNameInfo = parseSubscripts(implVar.varName);
55
+ let varTypeIndex = varTypeMap.get(implVar.varType);
56
+ if (varTypeIndex === void 0) {
57
+ varTypeIndex = varTypes.length;
58
+ varTypes.push(implVar.varType);
59
+ varTypeMap.set(implVar.varType, varTypeIndex);
60
+ }
61
+ let variableIndex = variableMap.get(implVar.varIndex);
62
+ if (variableIndex === void 0) {
63
+ variableIndex = variables.length;
64
+ variables.push({
65
+ n: varNameInfo.base,
66
+ i: varIdInfo.base,
67
+ x: implVar.varIndex
68
+ });
69
+ variableMap.set(implVar.varIndex, variableIndex);
70
+ }
71
+ let subscriptIndices;
72
+ let subscriptValues;
73
+ if (varIdInfo.subscripts.length > 0) {
74
+ subscriptIndices = [];
75
+ subscriptValues = implVar.subscriptIndices || [];
76
+ for (let i = 0; i < varIdInfo.subscripts.length; i++) {
77
+ const subscriptId = varIdInfo.subscripts[i];
78
+ const subscriptName = varNameInfo.subscripts[i];
79
+ let subscriptIndex = subscriptMap.get(subscriptId);
80
+ if (subscriptIndex === void 0) {
81
+ subscriptIndex = subscripts.length;
82
+ subscripts.push({
83
+ n: subscriptName,
84
+ i: subscriptId
85
+ });
86
+ subscriptMap.set(subscriptId, subscriptIndex);
87
+ }
88
+ subscriptIndices.push(subscriptIndex);
89
+ }
90
+ }
91
+ const instance = [varTypeIndex, variableIndex];
92
+ if (subscriptIndices) {
93
+ instance.push(...subscriptIndices, ...subscriptValues);
94
+ }
95
+ instances.push(instance);
96
+ }
97
+ varInstances[groupKey] = instances;
98
+ }
99
+ return {
100
+ subscripts,
101
+ variables,
102
+ varTypes,
103
+ varInstances
104
+ };
105
+ }
106
+ function decodeImplVars(encoded) {
107
+ const result = {};
108
+ for (const [groupKey, instances] of Object.entries(encoded.varInstances)) {
109
+ const implVars = [];
110
+ for (const instance of instances) {
111
+ const varType = encoded.varTypes[instance[0]];
112
+ const variable = encoded.variables[instance[1]];
113
+ let varId = variable.i;
114
+ let varName = variable.n;
115
+ if (instance.length > 2) {
116
+ const subscriptIds = [];
117
+ const subscriptNames = [];
118
+ const subscriptCount = (instance.length - 2) / 2;
119
+ const subscriptIndices = instance.slice(2, 2 + subscriptCount);
120
+ for (const subscriptIndex of subscriptIndices) {
121
+ const subscript = encoded.subscripts[subscriptIndex];
122
+ subscriptIds.push(subscript.i);
123
+ subscriptNames.push(subscript.n);
124
+ }
125
+ varId += `[${subscriptIds.join(",")}]`;
126
+ varName += `[${subscriptNames.join(",")}]`;
127
+ }
128
+ const implVar = {
129
+ varId,
130
+ varName,
131
+ varType,
132
+ varIndex: variable.x,
133
+ subscriptIndices: instance.length > 2 ? instance.slice(2 + (instance.length - 2) / 2) : void 0
134
+ };
135
+ implVars.push(implVar);
136
+ }
137
+ result[groupKey] = implVars;
138
+ }
139
+ return result;
140
+ }
141
+ function parseSubscripts(text) {
142
+ const bracketIndex = text.indexOf("[");
143
+ if (bracketIndex === -1) {
144
+ return { base: text, subscripts: [] };
145
+ }
146
+ const base = text.substring(0, bracketIndex);
147
+ const subscriptText = text.substring(bracketIndex + 1, text.lastIndexOf("]"));
148
+ const subscripts = subscriptText.split(",").map((s) => s.trim());
149
+ return { base, subscripts };
150
+ }
151
+
41
152
  // src/_shared/task-queue.ts
42
- var TaskQueue = class {
43
- constructor(processor) {
44
- this.processor = processor;
153
+ var TaskQueue = class _TaskQueue {
154
+ /**
155
+ * @param executors The map of available task executors.
156
+ */
157
+ constructor(executors) {
158
+ this.executors = executors;
45
159
  /** The queue of task keys, most recent at front. */
46
160
  this.taskKeyQueue = [];
47
161
  /** The map of tasks. */
48
162
  this.taskMap = /* @__PURE__ */ new Map();
163
+ // /** The set of keys for the executors that are currently processing tasks. */
164
+ // private readonly activeExecutorKeys: Set<TaskExecutorKey> = new Set()
165
+ /** The idle event listeners. */
166
+ this.idleListeners = [];
49
167
  /** Whether tasks are being processed. */
50
168
  this.processing = false;
51
169
  /** Whether `shutdown` has been called. */
52
170
  this.stopped = false;
53
171
  }
54
- addTask(key, input, onComplete) {
172
+ /**
173
+ * Initialize the shared `TaskQueue` instance.
174
+ *
175
+ * @param executors The map of available task executors.
176
+ */
177
+ static initialize(executors) {
178
+ if (executors.size === 0) {
179
+ throw new Error("Must provide at least one executor");
180
+ }
181
+ this.instance = new _TaskQueue(executors);
182
+ }
183
+ /**
184
+ * Get the shared `TaskQueue` instance.
185
+ */
186
+ static getInstance() {
187
+ if (!this.instance) {
188
+ throw new Error("TaskQueue not initialized; must call `initialize` first");
189
+ }
190
+ return this.instance;
191
+ }
192
+ /**
193
+ * Add a task to the queue.
194
+ *
195
+ * @param task The task to add.
196
+ */
197
+ addTask(task) {
55
198
  if (this.stopped) {
56
199
  return;
57
200
  }
58
- if (this.taskMap.has(key)) {
59
- throw new Error(`Task already added for key ${key}`);
201
+ if (this.taskMap.has(task.key)) {
202
+ throw new Error(`Task already added for key ${task.key}`);
60
203
  }
61
- this.taskKeyQueue.push(key);
62
- this.taskMap.set(key, {
63
- input,
64
- onComplete
65
- });
204
+ this.taskKeyQueue.push(task.key);
205
+ this.taskMap.set(task.key, task);
66
206
  this.processTasksIfNeeded();
67
207
  }
208
+ /**
209
+ * Cancel a task.
210
+ *
211
+ * @param taskKey The key of the task to cancel.
212
+ */
68
213
  cancelTask(taskKey) {
69
214
  const index = this.taskKeyQueue.indexOf(taskKey);
70
215
  if (index >= 0) {
@@ -72,9 +217,37 @@ var TaskQueue = class {
72
217
  }
73
218
  this.taskMap.delete(taskKey);
74
219
  }
220
+ /**
221
+ * Add an idle listener.
222
+ *
223
+ * @param listener The listener to add.
224
+ */
225
+ onIdle(listener) {
226
+ this.idleListeners.push(listener);
227
+ }
228
+ /**
229
+ * Remove an idle listener.
230
+ *
231
+ * @param listener The listener to remove.
232
+ */
233
+ removeIdleListener(listener) {
234
+ this.idleListeners.splice(this.idleListeners.indexOf(listener), 1);
235
+ }
236
+ /**
237
+ * Notify the idle listeners.
238
+ *
239
+ * @param error The error to notify the listeners with.
240
+ */
241
+ notifyIdle(error) {
242
+ for (const listener of this.idleListeners) {
243
+ listener(error);
244
+ }
245
+ }
246
+ /**
247
+ * Shutdown the task queue, cancelling all pending tasks.
248
+ */
75
249
  shutdown() {
76
250
  this.stopped = true;
77
- this.processing = false;
78
251
  this.taskKeyQueue.length = 0;
79
252
  this.taskMap.clear();
80
253
  }
@@ -82,75 +255,98 @@ var TaskQueue = class {
82
255
  if (!this.stopped && !this.processing) {
83
256
  this.processing = true;
84
257
  setTimeout(() => {
85
- this.processNextTask();
258
+ this.processNextTasks();
86
259
  });
87
260
  }
88
261
  }
89
- processNextTask() {
262
+ processNextTasks() {
90
263
  return __async(this, null, function* () {
91
- var _a, _b;
92
- const taskKey = this.taskKeyQueue.shift();
93
- if (!taskKey) {
264
+ const taskKeys = this.taskKeyQueue.splice(0, this.executors.size);
265
+ if (taskKeys.length === 0) {
266
+ this.processing = false;
267
+ if (!this.stopped) {
268
+ this.notifyIdle();
269
+ }
94
270
  return;
95
271
  }
96
- const task = this.taskMap.get(taskKey);
97
- if (task) {
272
+ const executeCalls = [];
273
+ const availableExecutorKeys = Array.from(this.executors.keys());
274
+ for (const taskKey of taskKeys) {
275
+ const task = this.taskMap.get(taskKey);
276
+ if (!task) {
277
+ continue;
278
+ }
98
279
  this.taskMap.delete(taskKey);
99
- } else {
100
- return;
280
+ const executorKey = availableExecutorKeys.shift();
281
+ const executor = this.executors.get(executorKey);
282
+ if (!executor) {
283
+ throw new Error(`No executor found for key ${executorKey}`);
284
+ }
285
+ executeCalls.push(executor.execute(task));
101
286
  }
102
- let output;
103
287
  try {
104
- output = yield this.processor.process(task.input);
288
+ yield Promise.all(executeCalls);
105
289
  } catch (e) {
106
290
  if (!this.stopped) {
107
291
  this.shutdown();
108
- (_a = this.onIdle) == null ? void 0 : _a.call(this, e);
292
+ this.notifyIdle(e);
109
293
  }
110
294
  return;
111
295
  }
112
- task.onComplete(output);
113
296
  if (this.taskKeyQueue.length > 0) {
114
297
  setTimeout(() => {
115
- this.processNextTask();
298
+ this.processNextTasks();
116
299
  });
117
300
  } else {
118
301
  this.processing = false;
119
302
  if (!this.stopped) {
120
- (_b = this.onIdle) == null ? void 0 : _b.call(this);
303
+ this.notifyIdle();
121
304
  }
122
305
  }
123
306
  });
124
307
  }
125
308
  };
309
+ function createExecutor(bundleModelL, bundleModelR) {
310
+ return {
311
+ execute: (task) => __async(null, null, function* () {
312
+ return task.process({
313
+ L: bundleModelL,
314
+ R: bundleModelR
315
+ });
316
+ })
317
+ };
318
+ }
126
319
 
127
320
  // src/check/check-data-coordinator.ts
128
321
  var CheckDataCoordinator = class {
129
- constructor(bundleModel) {
130
- this.bundleModel = bundleModel;
131
- this.taskQueue = new TaskQueue({
132
- process: (request) => __async(this, null, function* () {
133
- const result = yield this.bundleModel.getDatasetsForScenario(request.scenarioSpec, [request.datasetKey]);
134
- const dataset = result.datasetMap.get(request.datasetKey);
135
- return {
136
- dataset
137
- };
138
- })
139
- });
322
+ constructor(taskQueue) {
323
+ this.taskQueue = taskQueue;
140
324
  }
141
325
  requestDataset(requestKey, scenarioSpec, datasetKey, onResponse) {
142
- const request = {
143
- scenarioSpec,
144
- datasetKey
326
+ const task = {
327
+ key: requestKey,
328
+ kind: "check-data-coordinator",
329
+ process: (bundleModels) => __async(null, null, function* () {
330
+ const bundleModelR = bundleModels.R;
331
+ const result = yield bundleModelR.getDatasetsForScenario(scenarioSpec, [datasetKey]);
332
+ const dataset = result.datasetMap.get(datasetKey);
333
+ onResponse(dataset);
334
+ })
145
335
  };
146
- this.taskQueue.addTask(requestKey, request, (response) => {
147
- onResponse(response.dataset);
148
- });
336
+ this.taskQueue.addTask(task);
149
337
  }
150
338
  cancelRequest(key) {
151
339
  this.taskQueue.cancelTask(key);
152
340
  }
153
341
  };
342
+ function createCheckDataCoordinator() {
343
+ return new CheckDataCoordinator(TaskQueue.getInstance());
344
+ }
345
+ function createCheckDataCoordinatorForTests(bundleModel) {
346
+ const executor = createExecutor(void 0, bundleModel);
347
+ const taskQueue = new TaskQueue(/* @__PURE__ */ new Map([["test-executor-0", executor]]));
348
+ return new CheckDataCoordinator(taskQueue);
349
+ }
154
350
 
155
351
  // src/check/check-report.ts
156
352
  import assertNever2 from "assert-never";
@@ -212,6 +408,10 @@ function buildCheckReport(checkPlan, checkResults) {
212
408
  testStatus = "failed";
213
409
  scenarioStatus = "failed";
214
410
  datasetStatus = "failed";
411
+ } else if (checkResult.status === "skipped" && testStatus === "passed") {
412
+ testStatus = "skipped";
413
+ scenarioStatus = "skipped";
414
+ datasetStatus = "skipped";
215
415
  }
216
416
  }
217
417
  predicateReports.push(predicateReport(predicatePlan, checkKey, checkResult));
@@ -1147,16 +1347,6 @@ function actionForPredicate(predicateSpec) {
1147
1347
  };
1148
1348
  }
1149
1349
 
1150
- // src/_shared/combo.ts
1151
- function cartesianProductOf(arr) {
1152
- return arr.reduce(
1153
- (a, b) => {
1154
- return a.map((x) => b.map((y) => x.concat([y]))).reduce((v, w) => v.concat(w), []);
1155
- },
1156
- [[]]
1157
- );
1158
- }
1159
-
1160
1350
  // src/check/check-dataset.ts
1161
1351
  function expandDatasets(modelSpec, datasetSpec) {
1162
1352
  var _a;
@@ -1185,25 +1375,10 @@ function expandDatasets(modelSpec, datasetSpec) {
1185
1375
  name: match.outputVar.varName
1186
1376
  });
1187
1377
  } else if (match.implVar) {
1188
- const implVar = match.implVar;
1189
- if (implVar.dimensions.length > 0) {
1190
- const baseDatasetKey = match.datasetKey;
1191
- const subscripts = [...implVar.dimensions.map((dim) => dim.subscripts)];
1192
- const subscriptCombos = cartesianProductOf(subscripts);
1193
- for (const subscriptCombo of subscriptCombos) {
1194
- const subIdParts = subscriptCombo.map((sub) => `[${sub.id}]`).join("");
1195
- const subNameParts = subscriptCombo.map((sub) => sub.name).join(",");
1196
- checkDatasets.push({
1197
- datasetKey: `${baseDatasetKey}${subIdParts}`,
1198
- name: `${implVar.varName}[${subNameParts}]`
1199
- });
1200
- }
1201
- } else {
1202
- checkDatasets.push({
1203
- datasetKey: match.datasetKey,
1204
- name: implVar.varName
1205
- });
1206
- }
1378
+ checkDatasets.push({
1379
+ datasetKey: match.datasetKey,
1380
+ name: match.implVar.varName
1381
+ });
1207
1382
  }
1208
1383
  }
1209
1384
  return checkDatasets;
@@ -1611,12 +1786,18 @@ var CheckPlanner = class {
1611
1786
  this.dataRefs = /* @__PURE__ */ new Map();
1612
1787
  this.checkKey = 1;
1613
1788
  }
1614
- addAllChecks(checkSpec, simplifyScenarios) {
1789
+ addAllChecks(checkSpec, skipChecks) {
1790
+ function skipCheckKey(groupName, testName) {
1791
+ return `${groupName.toLowerCase()} :: ${testName.toLowerCase()}`;
1792
+ }
1793
+ const skipChecksSet = new Set(skipChecks.map((check) => skipCheckKey(check.groupName, check.testName)));
1615
1794
  for (const groupSpec of checkSpec.groups) {
1616
1795
  const groupName = groupSpec.describe;
1617
1796
  const planTests = [];
1618
1797
  for (const testSpec of groupSpec.tests) {
1619
1798
  const testName = testSpec.it;
1799
+ const shouldSkip = skipChecksSet.has(skipCheckKey(groupName, testName));
1800
+ const simplifyScenarios = false;
1620
1801
  const checkScenarios = expandScenarios(this.modelSpec, testSpec.scenarios || [], simplifyScenarios);
1621
1802
  const checkDatasets = [];
1622
1803
  for (const datasetSpec of testSpec.datasets) {
@@ -1657,7 +1838,8 @@ var CheckPlanner = class {
1657
1838
  scenario: checkScenario,
1658
1839
  dataset: checkDataset,
1659
1840
  action: checkAction,
1660
- dataRefs
1841
+ dataRefs,
1842
+ skip: shouldSkip
1661
1843
  });
1662
1844
  }
1663
1845
  planDatasets.push({
@@ -1794,6 +1976,7 @@ function checkSummaryFromReport(checkReport) {
1794
1976
  break;
1795
1977
  case "failed":
1796
1978
  case "error":
1979
+ case "skipped":
1797
1980
  predicateSummaries.push({
1798
1981
  checkKey: predicate.checkKey,
1799
1982
  result: predicate.result
@@ -1811,14 +1994,14 @@ function checkSummaryFromReport(checkReport) {
1811
1994
  predicateSummaries
1812
1995
  };
1813
1996
  }
1814
- function checkReportFromSummary(checkConfig, checkSummary) {
1997
+ function checkReportFromSummary(checkConfig, checkSummary, skipChecks = []) {
1815
1998
  const checkSpecResult = parseTestYaml(checkConfig.tests);
1816
1999
  if (checkSpecResult.isErr()) {
1817
2000
  return void 0;
1818
2001
  }
1819
2002
  const checkSpec = checkSpecResult.value;
1820
- const checkPlanner = new CheckPlanner(checkConfig.bundle.model.modelSpec);
1821
- checkPlanner.addAllChecks(checkSpec, false);
2003
+ const checkPlanner = new CheckPlanner(checkConfig.bundle.modelSpec);
2004
+ checkPlanner.addAllChecks(checkSpec, skipChecks);
1822
2005
  const checkPlan = checkPlanner.buildPlan();
1823
2006
  const checkResults = /* @__PURE__ */ new Map();
1824
2007
  for (const predicateSummary of checkSummary.predicateSummaries) {
@@ -1828,98 +2011,116 @@ function checkReportFromSummary(checkConfig, checkSummary) {
1828
2011
  }
1829
2012
 
1830
2013
  // src/comparison/run/comparison-data-coordinator.ts
1831
- import { assertNever as assertNever7 } from "assert-never";
1832
2014
  var ComparisonDataCoordinator = class {
1833
- constructor(bundleModelL, bundleModelR) {
1834
- this.bundleModelL = bundleModelL;
1835
- this.bundleModelR = bundleModelR;
1836
- this.taskQueue = new TaskQueue({
1837
- process: (request) => __async(this, null, function* () {
1838
- switch (request.kind) {
1839
- case "dataset":
1840
- return this.processDatasetRequest(request);
1841
- case "graph-data":
1842
- return this.processGraphDataRequest(request);
1843
- default:
1844
- assertNever7(request);
2015
+ constructor(taskQueue) {
2016
+ this.taskQueue = taskQueue;
2017
+ }
2018
+ /**
2019
+ * Request datasets from the two models.
2020
+ *
2021
+ * @param requestKey The unique key for the request.
2022
+ * @param sourceL The source of the first ("left") dataset. If "left", the datasets will
2023
+ * be fetched from the "left" bundle's model, otherwise they will be fetched from the
2024
+ * "right" bundle's model.
2025
+ * @param scenarioSpecL The scenario used for the first ("left") model of the comparison.
2026
+ * @param sourceR The source of the second ("right") dataset. If "left", the datasets
2027
+ * will be fetched from the "left" bundle's model, otherwise they will be fetched from
2028
+ * the "right" bundle's model.
2029
+ * @param scenarioSpecR The scenario used for the second ("right") model of the comparison.
2030
+ * @param graphId The keys of the datasets to be fetched.
2031
+ * @param onResponse The callback that will be called with the dataset maps.
2032
+ */
2033
+ requestDatasetMaps(requestKey, sourceL, scenarioSpecL, sourceR, scenarioSpecR, datasetKeys, onResponse) {
2034
+ function fetchDatasets(bundleModel, scenarioSpec) {
2035
+ return __async(this, null, function* () {
2036
+ if (scenarioSpec) {
2037
+ return bundleModel.getDatasetsForScenario(scenarioSpec, datasetKeys);
2038
+ } else {
2039
+ return void 0;
2040
+ }
2041
+ });
2042
+ }
2043
+ const task = {
2044
+ key: requestKey,
2045
+ kind: "comparison-data-coordinator",
2046
+ process: (bundleModels) => __async(null, null, function* () {
2047
+ const modelL = sourceL === "left" ? bundleModels.L : bundleModels.R;
2048
+ const modelR = sourceR === "left" ? bundleModels.L : bundleModels.R;
2049
+ let resultL;
2050
+ let resultR;
2051
+ if (modelL === modelR) {
2052
+ resultL = yield fetchDatasets(modelL, scenarioSpecL);
2053
+ resultR = yield fetchDatasets(modelR, scenarioSpecR);
2054
+ } else {
2055
+ const results = yield Promise.all([
2056
+ fetchDatasets(modelL, scenarioSpecL),
2057
+ fetchDatasets(modelR, scenarioSpecR)
2058
+ ]);
2059
+ resultL = results[0];
2060
+ resultR = results[1];
1845
2061
  }
2062
+ onResponse(resultL == null ? void 0 : resultL.datasetMap, resultR == null ? void 0 : resultR.datasetMap);
1846
2063
  })
1847
- });
1848
- }
1849
- processDatasetRequest(request) {
1850
- return __async(this, null, function* () {
1851
- function fetchDatasets(bundleModel, scenarioSpec) {
1852
- return __async(this, null, function* () {
1853
- if (scenarioSpec) {
1854
- return bundleModel.getDatasetsForScenario(scenarioSpec, request.datasetKeys);
1855
- } else {
1856
- return void 0;
1857
- }
1858
- });
1859
- }
1860
- const [resultL, resultR] = yield Promise.all([
1861
- fetchDatasets(this.bundleModelL, request.scenarioSpecL),
1862
- fetchDatasets(this.bundleModelR, request.scenarioSpecR)
1863
- ]);
1864
- return {
1865
- kind: "dataset",
1866
- datasetMapL: resultL == null ? void 0 : resultL.datasetMap,
1867
- datasetMapR: resultR == null ? void 0 : resultR.datasetMap
1868
- };
1869
- });
1870
- }
1871
- processGraphDataRequest(request) {
1872
- return __async(this, null, function* () {
1873
- function fetchGraphData(bundleModel, scenarioSpec) {
1874
- return __async(this, null, function* () {
1875
- if (scenarioSpec) {
1876
- return bundleModel.getGraphDataForScenario(scenarioSpec, request.graphId);
1877
- } else {
1878
- return void 0;
1879
- }
1880
- });
1881
- }
1882
- const [graphDataL, graphDataR] = yield Promise.all([
1883
- fetchGraphData(this.bundleModelL, request.scenarioSpecL),
1884
- fetchGraphData(this.bundleModelR, request.scenarioSpecR)
1885
- ]);
1886
- return {
1887
- kind: "graph-data",
1888
- graphDataL,
1889
- graphDataR
1890
- };
1891
- });
1892
- }
1893
- requestDatasetMaps(requestKey, scenarioSpecL, scenarioSpecR, datasetKeys, onResponse) {
1894
- const request = {
1895
- kind: "dataset",
1896
- scenarioSpecL,
1897
- scenarioSpecR,
1898
- datasetKeys
1899
2064
  };
1900
- this.taskQueue.addTask(requestKey, request, (response) => {
1901
- if (response.kind === "dataset") {
1902
- onResponse(response.datasetMapL, response.datasetMapR);
1903
- }
1904
- });
2065
+ this.taskQueue.addTask(task);
1905
2066
  }
1906
- requestGraphData(requestKey, scenarioSpecL, scenarioSpecR, graphId, onResponse) {
1907
- const request = {
1908
- kind: "graph-data",
1909
- scenarioSpecL,
1910
- scenarioSpecR,
1911
- graphId
2067
+ /**
2068
+ * Request graph data from the two models.
2069
+ *
2070
+ * @param requestKey The unique key for the request.
2071
+ * @param sourceL The source of the first ("left") dataset. If "left", the datasets will
2072
+ * be fetched from the "left" bundle's model, otherwise they will be fetched from the
2073
+ * "right" bundle's model.
2074
+ * @param scenarioSpecL The scenario used for the first ("left") model of the comparison.
2075
+ * @param sourceR The source of the second ("right") dataset. If "left", the datasets
2076
+ * will be fetched from the "left" bundle's model, otherwise they will be fetched from
2077
+ * the "right" bundle's model.
2078
+ * @param scenarioSpecR The scenario used for the second ("right") model of the comparison.
2079
+ * @param graphId The ID of the graph for which data will be fetched.
2080
+ * @param onResponse The callback that will be called with the graph data.
2081
+ */
2082
+ requestGraphData(requestKey, sourceL, scenarioSpecL, sourceR, scenarioSpecR, graphId, onResponse) {
2083
+ function fetchGraphData(bundleModel, scenarioSpec) {
2084
+ return __async(this, null, function* () {
2085
+ var _a;
2086
+ if (scenarioSpec) {
2087
+ return (_a = bundleModel.getGraphDataForScenario) == null ? void 0 : _a.call(bundleModel, scenarioSpec, graphId);
2088
+ } else {
2089
+ return void 0;
2090
+ }
2091
+ });
2092
+ }
2093
+ const task = {
2094
+ key: requestKey,
2095
+ kind: "comparison-data-coordinator",
2096
+ process: (bundleModels) => __async(null, null, function* () {
2097
+ const modelL = sourceL === "left" ? bundleModels.L : bundleModels.R;
2098
+ const modelR = sourceR === "left" ? bundleModels.L : bundleModels.R;
2099
+ let graphDataL;
2100
+ let graphDataR;
2101
+ if (modelL === modelR) {
2102
+ graphDataL = yield fetchGraphData(modelL, scenarioSpecL);
2103
+ graphDataR = yield fetchGraphData(modelR, scenarioSpecR);
2104
+ } else {
2105
+ const results = yield Promise.all([
2106
+ fetchGraphData(modelL, scenarioSpecL),
2107
+ fetchGraphData(modelR, scenarioSpecR)
2108
+ ]);
2109
+ graphDataL = results[0];
2110
+ graphDataR = results[1];
2111
+ }
2112
+ onResponse(graphDataL, graphDataR);
2113
+ })
1912
2114
  };
1913
- this.taskQueue.addTask(requestKey, request, (response) => {
1914
- if (response.kind === "graph-data") {
1915
- onResponse(response.graphDataL, response.graphDataR);
1916
- }
1917
- });
2115
+ this.taskQueue.addTask(task);
1918
2116
  }
1919
2117
  cancelRequest(key) {
1920
2118
  this.taskQueue.cancelTask(key);
1921
2119
  }
1922
2120
  };
2121
+ function createComparisonDataCoordinator() {
2122
+ return new ComparisonDataCoordinator(TaskQueue.getInstance());
2123
+ }
1923
2124
 
1924
2125
  // src/comparison/diff-datasets/diff-datasets.ts
1925
2126
  function diffDatasets(datasetL, datasetR) {
@@ -2069,14 +2270,14 @@ function diffGraphs(graphL, graphR, scenarioKey, testSummaries) {
2069
2270
 
2070
2271
  // src/comparison/report/comparison-reporting.ts
2071
2272
  function comparisonSummaryFromReport(comparisonReport) {
2273
+ var _a, _b;
2072
2274
  const terseSummaries = [];
2073
2275
  for (const r of comparisonReport.testReports) {
2074
- if (r.diffReport.validity === "both" && r.diffReport.maxDiff > 0) {
2075
- terseSummaries.push({
2076
- s: r.scenarioKey,
2077
- d: r.datasetKey,
2078
- md: r.diffReport.maxDiff
2079
- });
2276
+ const baselineMaxDiff = (_a = r.baselineDiffReport) == null ? void 0 : _a.maxDiff;
2277
+ const baselineAvgDiff = (_b = r.baselineDiffReport) == null ? void 0 : _b.avgDiff;
2278
+ const summary = testSummaryFromReport(r, baselineMaxDiff, baselineAvgDiff);
2279
+ if (summary) {
2280
+ terseSummaries.push(summary);
2080
2281
  }
2081
2282
  }
2082
2283
  return {
@@ -2085,6 +2286,43 @@ function comparisonSummaryFromReport(comparisonReport) {
2085
2286
  perfReportR: comparisonReport.perfReportR
2086
2287
  };
2087
2288
  }
2289
+ function testSummaryFromReport(r, baselineMaxDiff, baselineAvgDiff) {
2290
+ var _a;
2291
+ function baselineRelativeDiff(diffValue, baselineDiffValue) {
2292
+ if (baselineDiffValue !== void 0) {
2293
+ const epsilon = 1e-6;
2294
+ if (baselineDiffValue === 0) {
2295
+ baselineDiffValue = epsilon;
2296
+ }
2297
+ return diffValue / baselineDiffValue;
2298
+ } else {
2299
+ if (diffValue === 0) {
2300
+ return 0;
2301
+ } else {
2302
+ return 1;
2303
+ }
2304
+ }
2305
+ }
2306
+ if (r.diffReport === void 0) {
2307
+ return {
2308
+ s: r.scenarioKey,
2309
+ d: r.datasetKey
2310
+ };
2311
+ } else if (((_a = r.diffReport) == null ? void 0 : _a.validity) === "both" && r.diffReport.maxDiff > 0) {
2312
+ const maxDiffRelativeToBaseline = baselineRelativeDiff(r.diffReport.maxDiff, baselineMaxDiff);
2313
+ const avgDiffRelativeToBaseline = baselineRelativeDiff(r.diffReport.avgDiff, baselineAvgDiff);
2314
+ return {
2315
+ s: r.scenarioKey,
2316
+ d: r.datasetKey,
2317
+ md: r.diffReport.maxDiff,
2318
+ ad: r.diffReport.avgDiff,
2319
+ mdb: maxDiffRelativeToBaseline,
2320
+ adb: avgDiffRelativeToBaseline
2321
+ };
2322
+ } else {
2323
+ return void 0;
2324
+ }
2325
+ }
2088
2326
  function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2089
2327
  const existingSummaries = /* @__PURE__ */ new Map();
2090
2328
  for (const summary of terseSummaries) {
@@ -2097,24 +2335,33 @@ function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2097
2335
  for (const datasetKey of datasetKeys) {
2098
2336
  const key = `${scenario.key}::${datasetKey}`;
2099
2337
  const existingSummary = existingSummaries.get(key);
2100
- const maxDiff = (existingSummary == null ? void 0 : existingSummary.md) || 0;
2101
- allTestSummaries.push({
2102
- s: scenario.key,
2103
- d: datasetKey,
2104
- md: maxDiff
2105
- });
2338
+ if (existingSummary) {
2339
+ allTestSummaries.push(existingSummary);
2340
+ } else {
2341
+ allTestSummaries.push({
2342
+ s: scenario.key,
2343
+ d: datasetKey,
2344
+ md: 0,
2345
+ ad: 0,
2346
+ mdb: 0,
2347
+ adb: 0
2348
+ });
2349
+ }
2106
2350
  }
2107
2351
  }
2108
2352
  return allTestSummaries;
2109
2353
  }
2110
2354
 
2111
2355
  // src/comparison/report/buckets.ts
2112
- function getBucketIndex(diffPct, thresholds) {
2113
- if (diffPct === 0) {
2356
+ function getBucketIndex(diff, thresholds) {
2357
+ if (diff === void 0) {
2358
+ return thresholds.length + 2;
2359
+ }
2360
+ if (diff === 0) {
2114
2361
  return 0;
2115
2362
  }
2116
2363
  for (let i = 0; i < thresholds.length; i++) {
2117
- if (diffPct < thresholds[i]) {
2364
+ if (diff < thresholds[i]) {
2118
2365
  return i + 1;
2119
2366
  }
2120
2367
  }
@@ -2122,14 +2369,32 @@ function getBucketIndex(diffPct, thresholds) {
2122
2369
  }
2123
2370
 
2124
2371
  // src/comparison/report/comparison-group-scores.ts
2125
- function getScoresForTestSummaries(testSummaries, thresholds) {
2126
- const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2127
- const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2372
+ function getScoresForTestSummaries(testSummaries, thresholds, sortMode) {
2373
+ const diffCountByBucket = Array(thresholds.length + 3).fill(0);
2374
+ const totalDiffByBucket = Array(thresholds.length + 3).fill(0);
2128
2375
  let totalDiffCount = 0;
2376
+ let valueKey;
2377
+ switch (sortMode) {
2378
+ case "max-diff":
2379
+ valueKey = "md";
2380
+ break;
2381
+ case "avg-diff":
2382
+ valueKey = "ad";
2383
+ break;
2384
+ case "max-diff-relative":
2385
+ valueKey = "mdb";
2386
+ break;
2387
+ case "avg-diff-relative":
2388
+ valueKey = "adb";
2389
+ break;
2390
+ }
2129
2391
  for (const testSummary of testSummaries) {
2130
- const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2392
+ const value = testSummary[valueKey];
2393
+ const bucketIndex = getBucketIndex(value, thresholds);
2131
2394
  diffCountByBucket[bucketIndex]++;
2132
- totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2395
+ if (value !== void 0) {
2396
+ totalDiffByBucket[bucketIndex] += value;
2397
+ }
2133
2398
  totalDiffCount++;
2134
2399
  }
2135
2400
  let diffPercentByBucket;
@@ -2140,20 +2405,20 @@ function getScoresForTestSummaries(testSummaries, thresholds) {
2140
2405
  }
2141
2406
  return {
2142
2407
  totalDiffCount,
2143
- totalMaxDiffByBucket,
2408
+ totalDiffByBucket,
2144
2409
  diffCountByBucket,
2145
2410
  diffPercentByBucket
2146
2411
  };
2147
2412
  }
2148
2413
 
2149
2414
  // src/comparison/report/comparison-grouping.ts
2150
- import { assertNever as assertNever8 } from "assert-never";
2151
- function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2415
+ import { assertNever as assertNever7 } from "assert-never";
2416
+ function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries, sortMode) {
2152
2417
  const allTestSummaries = restoreFromTerseSummaries(comparisonConfig, terseSummaries);
2153
2418
  const groupsByScenario = groupComparisonTestSummaries(allTestSummaries, "by-scenario");
2154
- const byScenario = categorizeComparisonGroups(comparisonConfig, [...groupsByScenario.values()]);
2419
+ const byScenario = categorizeComparisonGroups(comparisonConfig, [...groupsByScenario.values()], sortMode);
2155
2420
  const groupsByDataset = groupComparisonTestSummaries(allTestSummaries, "by-dataset");
2156
- const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()]);
2421
+ const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()], sortMode);
2157
2422
  return {
2158
2423
  allTestSummaries,
2159
2424
  byScenario,
@@ -2172,7 +2437,7 @@ function groupComparisonTestSummaries(testSummaries, groupKind) {
2172
2437
  groupKey = testSummary.s;
2173
2438
  break;
2174
2439
  default:
2175
- assertNever8(groupKind);
2440
+ assertNever7(groupKind);
2176
2441
  }
2177
2442
  const group = groups.get(groupKey);
2178
2443
  if (group) {
@@ -2187,7 +2452,7 @@ function groupComparisonTestSummaries(testSummaries, groupKind) {
2187
2452
  }
2188
2453
  return groups;
2189
2454
  }
2190
- function categorizeComparisonGroups(comparisonConfig, allGroups) {
2455
+ function categorizeComparisonGroups(comparisonConfig, allGroups, sortMode) {
2191
2456
  const allGroupSummaries = /* @__PURE__ */ new Map();
2192
2457
  const withErrors = [];
2193
2458
  const onlyInLeft = [];
@@ -2197,7 +2462,9 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2197
2462
  function addSummaryForGroup(group, root, validInL, validInR) {
2198
2463
  let scores;
2199
2464
  if (validInL && validInR) {
2200
- scores = getScoresForTestSummaries(group.testSummaries, comparisonConfig.thresholds);
2465
+ const isRelativeMode = sortMode === "max-diff-relative" || sortMode === "avg-diff-relative";
2466
+ const thresholds = isRelativeMode ? comparisonConfig.ratioThresholds : comparisonConfig.thresholds;
2467
+ scores = getScoresForTestSummaries(group.testSummaries, thresholds, sortMode);
2201
2468
  }
2202
2469
  const groupSummary = {
2203
2470
  root,
@@ -2206,7 +2473,9 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2206
2473
  };
2207
2474
  allGroupSummaries.set(group.key, groupSummary);
2208
2475
  if (validInL && validInR) {
2209
- if (scores.totalDiffCount !== scores.diffCountByBucket[0]) {
2476
+ const noDiffCount = scores.diffCountByBucket[0];
2477
+ const skippedCount = scores.diffCountByBucket[5];
2478
+ if (scores.totalDiffCount !== noDiffCount + skippedCount) {
2210
2479
  withDiffs.push(groupSummary);
2211
2480
  } else {
2212
2481
  withoutDiffs.push(groupSummary);
@@ -2236,7 +2505,7 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2236
2505
  break;
2237
2506
  }
2238
2507
  default:
2239
- assertNever8(group.kind);
2508
+ assertNever7(group.kind);
2240
2509
  }
2241
2510
  }
2242
2511
  if (withDiffs.length > 1) {
@@ -2298,13 +2567,13 @@ function sortScenarioGroupSummaries(summaries) {
2298
2567
  });
2299
2568
  }
2300
2569
  function compareScores(a, b) {
2301
- if (a.totalMaxDiffByBucket.length !== b.totalMaxDiffByBucket.length) {
2570
+ if (a.totalDiffByBucket.length !== b.totalDiffByBucket.length) {
2302
2571
  return 0;
2303
2572
  }
2304
- const len = a.totalMaxDiffByBucket.length;
2573
+ const len = a.totalDiffByBucket.length;
2305
2574
  for (let i = len - 1; i >= 0; i--) {
2306
- const aTotal = a.totalMaxDiffByBucket[i];
2307
- const bTotal = b.totalMaxDiffByBucket[i];
2575
+ const aTotal = a.totalDiffByBucket[i];
2576
+ const bTotal = b.totalDiffByBucket[i];
2308
2577
  if (aTotal > bTotal) {
2309
2578
  return 1;
2310
2579
  } else if (aTotal < bTotal) {
@@ -2316,7 +2585,7 @@ function compareScores(a, b) {
2316
2585
 
2317
2586
  // src/comparison/config/parse/comparison-parser.ts
2318
2587
  import Ajv2 from "ajv";
2319
- import assertNever9 from "assert-never";
2588
+ import assertNever8 from "assert-never";
2320
2589
  import { err as err2, ok as ok2 } from "neverthrow";
2321
2590
  import yaml2 from "yaml";
2322
2591
 
@@ -2852,7 +3121,7 @@ function parseComparisonSpecs(specSource) {
2852
3121
  parsed = yaml2.parse(specSource.content);
2853
3122
  break;
2854
3123
  default:
2855
- assertNever9(specSource.kind);
3124
+ assertNever8(specSource.kind);
2856
3125
  }
2857
3126
  if (validate(parsed)) {
2858
3127
  for (const specItem of parsed) {
@@ -3079,7 +3348,7 @@ function viewGroupSpecFromParsed(parsedViewGroup) {
3079
3348
  }
3080
3349
 
3081
3350
  // src/comparison/config/resolve/comparison-resolver.ts
3082
- import { assertNever as assertNever11 } from "assert-never";
3351
+ import { assertNever as assertNever10 } from "assert-never";
3083
3352
 
3084
3353
  // src/bundle/model-inputs.ts
3085
3354
  var ModelInputs = class {
@@ -3142,7 +3411,7 @@ var ModelInputs = class {
3142
3411
  };
3143
3412
 
3144
3413
  // src/comparison/config/resolve/comparison-scenario-specs.ts
3145
- import { assertNever as assertNever10 } from "assert-never";
3414
+ import { assertNever as assertNever9 } from "assert-never";
3146
3415
  function scenarioSpecsFromSettings(settings) {
3147
3416
  switch (settings.kind) {
3148
3417
  case "all-inputs-settings": {
@@ -3155,7 +3424,7 @@ function scenarioSpecsFromSettings(settings) {
3155
3424
  return [specL, specR];
3156
3425
  }
3157
3426
  default:
3158
- assertNever10(settings);
3427
+ assertNever9(settings);
3159
3428
  }
3160
3429
  }
3161
3430
  function scenarioSpecFromInputs(inputs, side) {
@@ -3374,7 +3643,7 @@ function resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genK
3374
3643
  ];
3375
3644
  }
3376
3645
  default:
3377
- assertNever11(scenarioSpec);
3646
+ assertNever10(scenarioSpec);
3378
3647
  }
3379
3648
  }
3380
3649
  function resolveScenarioMatrix(modelInputsL, modelInputsR, genKey) {
@@ -3430,7 +3699,7 @@ function resolveScenarioForInputSpecs(modelInputsL, modelInputsR, key, id, title
3430
3699
  case "input-at-value":
3431
3700
  return resolveInputForName(modelInputsL, modelInputsR, inputSpec.inputName, inputSpec.value);
3432
3701
  default:
3433
- assertNever11(inputSpec);
3702
+ assertNever10(inputSpec);
3434
3703
  }
3435
3704
  });
3436
3705
  const settings = {
@@ -3463,7 +3732,7 @@ function resolveScenarioForDistinctInputSpecs(modelInputsL, modelInputsR, key, i
3463
3732
  inputState = resolveInputForNameInModel(modelInputs, inputSpec.inputName, inputSpec.value);
3464
3733
  break;
3465
3734
  default:
3466
- assertNever11(inputSpec);
3735
+ assertNever10(inputSpec);
3467
3736
  }
3468
3737
  if (inputState.error !== void 0) {
3469
3738
  inputsWithErrors.push({
@@ -3679,7 +3948,7 @@ function inputValueAtPosition2(inputVar, position) {
3679
3948
  case "at-maximum":
3680
3949
  return inputVar.maxValue;
3681
3950
  default:
3682
- assertNever11(position);
3951
+ assertNever10(position);
3683
3952
  }
3684
3953
  }
3685
3954
  var ResolvedScenarioGroups = class {
@@ -3738,7 +4007,7 @@ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, grap
3738
4007
  return [...graphIds];
3739
4008
  }
3740
4009
  default:
3741
- assertNever11(graphsSpec.preset);
4010
+ assertNever10(graphsSpec.preset);
3742
4011
  }
3743
4012
  }
3744
4013
  // eslint-disable-next-line no-fallthrough
@@ -3752,7 +4021,7 @@ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, grap
3752
4021
  return groupSpec.graphIds;
3753
4022
  }
3754
4023
  default:
3755
- assertNever11(graphsSpec);
4024
+ assertNever10(graphsSpec);
3756
4025
  }
3757
4026
  }
3758
4027
  function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphIds, graphOrder) {
@@ -3926,7 +4195,7 @@ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolved
3926
4195
  views.push(resolveViewForScenario(void 0, void 0, scenario, graphIds, graphOrder));
3927
4196
  break;
3928
4197
  default:
3929
- assertNever11(scenario);
4198
+ assertNever10(scenario);
3930
4199
  }
3931
4200
  }
3932
4201
  } else {
@@ -3935,13 +4204,13 @@ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolved
3935
4204
  break;
3936
4205
  }
3937
4206
  default:
3938
- assertNever11(refSpec);
4207
+ assertNever10(refSpec);
3939
4208
  }
3940
4209
  }
3941
4210
  break;
3942
4211
  }
3943
4212
  default:
3944
- assertNever11(viewGroupSpec);
4213
+ assertNever10(viewGroupSpec);
3945
4214
  }
3946
4215
  return {
3947
4216
  kind: "view-group",
@@ -4004,9 +4273,9 @@ var ComparisonDatasetsImpl = class {
4004
4273
  }
4005
4274
  const allOutputVarKeysSet = /* @__PURE__ */ new Set();
4006
4275
  const modelOutputVarKeysSet = /* @__PURE__ */ new Set();
4007
- function addOutputVars(outputVars, handleRenames) {
4276
+ function addOutputVars(outputVars, handleRenames2) {
4008
4277
  outputVars.forEach((outputVar, key) => {
4009
- const remappedKey = handleRenames ? leftKeyForRightKey(key) : key;
4278
+ const remappedKey = handleRenames2 ? leftKeyForRightKey(key) : key;
4010
4279
  allOutputVarKeysSet.add(remappedKey);
4011
4280
  if (outputVar.sourceName === void 0) {
4012
4281
  modelOutputVarKeysSet.add(remappedKey);
@@ -4120,109 +4389,41 @@ var ComparisonScenariosImpl = class {
4120
4389
  }
4121
4390
  };
4122
4391
 
4123
- // src/config/synchronized-model.ts
4124
- function synchronizedBundleModel(sourceModel) {
4125
- const promiseQueue = new PromiseQueue();
4126
- return {
4127
- modelSpec: sourceModel.modelSpec,
4128
- getDatasetsForScenario: (scenarioSpec, datasetKeys) => {
4129
- return promiseQueue.add(() => sourceModel.getDatasetsForScenario(scenarioSpec, datasetKeys));
4130
- },
4131
- getGraphDataForScenario: (scenarioSpec, graphId) => {
4132
- return promiseQueue.add(() => sourceModel.getGraphDataForScenario(scenarioSpec, graphId));
4133
- },
4134
- getGraphLinksForScenario: sourceModel.getGraphLinksForScenario.bind(sourceModel)
4135
- };
4136
- }
4137
- var PromiseQueue = class {
4138
- constructor() {
4139
- this.tasks = [];
4140
- this.runningCount = 0;
4141
- }
4142
- add(f) {
4143
- return new Promise((resolve, reject) => {
4144
- const run = () => __async(this, null, function* () {
4145
- this.runningCount++;
4146
- const promise = f();
4147
- try {
4148
- const result = yield promise;
4149
- resolve(result);
4150
- } catch (e) {
4151
- reject(e);
4152
- } finally {
4153
- this.runningCount--;
4154
- this.runNext();
4155
- }
4156
- });
4157
- if (this.runningCount < 1) {
4158
- run();
4159
- } else {
4160
- this.tasks.push(run);
4161
- }
4162
- });
4163
- }
4164
- runNext() {
4165
- if (this.tasks.length > 0) {
4166
- const task = this.tasks.shift();
4167
- if (task) {
4168
- task();
4169
- }
4170
- }
4171
- }
4172
- };
4173
-
4174
4392
  // src/config/config.ts
4175
4393
  function createConfig(options) {
4176
4394
  return __async(this, null, function* () {
4177
- var _a;
4178
- const origCurrentBundle = yield loadSynchronized(options.current);
4395
+ var _a, _b, _c, _d, _e;
4396
+ let concurrentModels;
4397
+ if (options.concurrency === void 0) {
4398
+ concurrentModels = 1;
4399
+ } else if (options.concurrency === 0) {
4400
+ let coreCount;
4401
+ if (typeof navigator !== "undefined") {
4402
+ coreCount = navigator.hardwareConcurrency;
4403
+ }
4404
+ if (coreCount === void 0 || coreCount < 1) {
4405
+ coreCount = 1;
4406
+ }
4407
+ concurrentModels = Math.max(1, Math.floor(coreCount / 2));
4408
+ } else {
4409
+ concurrentModels = Math.max(1, options.concurrency);
4410
+ }
4411
+ const origCurrentBundle = yield loadBundle(options.current, concurrentModels);
4179
4412
  let currentBundle;
4180
4413
  let comparisonConfig;
4181
4414
  if (options.comparison === void 0) {
4182
4415
  currentBundle = origCurrentBundle;
4183
4416
  } else {
4184
- const baselineBundle = yield loadSynchronized(options.comparison.baseline);
4185
- const renamedDatasetKeys = (_a = options.comparison.datasets) == null ? void 0 : _a.renamedDatasetKeys;
4186
- const invertedRenamedKeys = /* @__PURE__ */ new Map();
4187
- renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
4188
- invertedRenamedKeys.set(newKey, oldKey);
4189
- });
4190
- const rightKeyForLeftKey = (leftKey) => {
4191
- return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
4192
- };
4193
- const leftKeyForRightKey = (rightKey) => {
4194
- return invertedRenamedKeys.get(rightKey) || rightKey;
4195
- };
4196
- const origBundleModelR = origCurrentBundle.model;
4197
- const adjBundleModelR = {
4198
- modelSpec: origBundleModelR.modelSpec,
4199
- getDatasetsForScenario: (scenarioSpec, datasetKeys) => __async(this, null, function* () {
4200
- const rightKeys = datasetKeys.map(rightKeyForLeftKey);
4201
- const result = yield origBundleModelR.getDatasetsForScenario(scenarioSpec, rightKeys);
4202
- const mapWithRightKeys = result.datasetMap;
4203
- const mapWithLeftKeys = /* @__PURE__ */ new Map();
4204
- for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
4205
- const leftKey = leftKeyForRightKey(rightKey);
4206
- mapWithLeftKeys.set(leftKey, dataset);
4207
- }
4208
- return {
4209
- datasetMap: mapWithLeftKeys,
4210
- modelRunTime: result.modelRunTime
4211
- };
4212
- }),
4213
- getGraphDataForScenario: origBundleModelR.getGraphDataForScenario.bind(origBundleModelR),
4214
- getGraphLinksForScenario: origBundleModelR.getGraphLinksForScenario.bind(origBundleModelR)
4215
- };
4216
- currentBundle = __spreadProps(__spreadValues({}, origCurrentBundle), {
4217
- model: adjBundleModelR
4218
- });
4219
- const modelSpecL = baselineBundle.model.modelSpec;
4220
- const modelSpecR = currentBundle.model.modelSpec;
4417
+ const baselineBundle = yield loadBundle(options.comparison.baseline, concurrentModels);
4418
+ currentBundle = handleRenames(origCurrentBundle, (_a = options.comparison.datasets) == null ? void 0 : _a.renamedDatasetKeys);
4419
+ const modelSpecL = baselineBundle.modelSpec;
4420
+ const modelSpecR = currentBundle.modelSpec;
4221
4421
  const comparisonDefs = resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, options.comparison.specs);
4222
4422
  comparisonConfig = {
4223
4423
  bundleL: baselineBundle,
4224
4424
  bundleR: currentBundle,
4225
- thresholds: options.comparison.thresholds,
4425
+ thresholds: (_b = options.comparison.thresholds) != null ? _b : [1, 5, 10],
4426
+ ratioThresholds: (_c = options.comparison.ratioThresholds) != null ? _c : [1, 2, 3],
4226
4427
  scenarios: getComparisonScenarios(comparisonDefs.scenarios),
4227
4428
  datasets: getComparisonDatasets(modelSpecL, modelSpecR, options.comparison.datasets),
4228
4429
  viewGroups: comparisonDefs.viewGroups,
@@ -4233,26 +4434,77 @@ function createConfig(options) {
4233
4434
  bundle: currentBundle,
4234
4435
  tests: options.check.tests
4235
4436
  };
4437
+ const executors = /* @__PURE__ */ new Map();
4438
+ for (let i = 0; i < checkConfig.bundle.models.length; i++) {
4439
+ const bundleModelL = (_d = comparisonConfig == null ? void 0 : comparisonConfig.bundleL.models) == null ? void 0 : _d[i];
4440
+ const bundleModelR = ((_e = comparisonConfig == null ? void 0 : comparisonConfig.bundleR.models) == null ? void 0 : _e[i]) || checkConfig.bundle.models[i];
4441
+ const executor = createExecutor(bundleModelL, bundleModelR);
4442
+ executors.set(`executor-${i}`, executor);
4443
+ }
4444
+ TaskQueue.initialize(executors);
4236
4445
  return {
4237
4446
  check: checkConfig,
4238
4447
  comparison: comparisonConfig
4239
4448
  };
4240
4449
  });
4241
4450
  }
4242
- function loadSynchronized(sourceBundle) {
4451
+ function loadBundle(bundle, concurrentModels) {
4243
4452
  return __async(this, null, function* () {
4244
- const sourceModel = yield sourceBundle.bundle.initModel();
4245
- const synchronizedModel = synchronizedBundleModel(sourceModel);
4453
+ const initCalls = Array.from({ length: concurrentModels }, () => bundle.bundle.initModel());
4454
+ const models = yield Promise.all(initCalls);
4246
4455
  return {
4247
- name: sourceBundle.name,
4248
- version: sourceBundle.bundle.version,
4249
- model: synchronizedModel
4456
+ name: bundle.name,
4457
+ version: bundle.bundle.version,
4458
+ modelSpec: bundle.bundle.modelSpec,
4459
+ models
4460
+ };
4461
+ });
4462
+ }
4463
+ function handleRenames(origCurrentBundle, renamedDatasetKeys) {
4464
+ if (renamedDatasetKeys === void 0 || renamedDatasetKeys.size === 0) {
4465
+ return origCurrentBundle;
4466
+ }
4467
+ const invertedRenamedKeys = /* @__PURE__ */ new Map();
4468
+ renamedDatasetKeys.forEach((newKey, oldKey) => {
4469
+ invertedRenamedKeys.set(newKey, oldKey);
4470
+ });
4471
+ const rightKeyForLeftKey = (leftKey) => {
4472
+ return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
4473
+ };
4474
+ const leftKeyForRightKey = (rightKey) => {
4475
+ return invertedRenamedKeys.get(rightKey) || rightKey;
4476
+ };
4477
+ function wrapModel(origBundleModelR) {
4478
+ var _a, _b, _c;
4479
+ return {
4480
+ modelSpec: origBundleModelR.modelSpec,
4481
+ getDatasetsForScenario: (scenarioSpec, datasetKeys) => __async(null, null, function* () {
4482
+ const rightKeys = datasetKeys.map(rightKeyForLeftKey);
4483
+ const result = yield origBundleModelR.getDatasetsForScenario(scenarioSpec, rightKeys);
4484
+ const mapWithRightKeys = result.datasetMap;
4485
+ const mapWithLeftKeys = /* @__PURE__ */ new Map();
4486
+ for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
4487
+ const leftKey = leftKeyForRightKey(rightKey);
4488
+ mapWithLeftKeys.set(leftKey, dataset);
4489
+ }
4490
+ return {
4491
+ datasetMap: mapWithLeftKeys,
4492
+ modelRunTime: result.modelRunTime
4493
+ };
4494
+ }),
4495
+ getGraphDataForScenario: (_a = origBundleModelR.getGraphDataForScenario) == null ? void 0 : _a.bind(origBundleModelR),
4496
+ getGraphLinksForScenario: (_b = origBundleModelR.getGraphLinksForScenario) == null ? void 0 : _b.bind(origBundleModelR),
4497
+ createGraphView: (_c = origBundleModelR.createGraphView) == null ? void 0 : _c.bind(origBundleModelR)
4250
4498
  };
4499
+ }
4500
+ const wrappedModels = origCurrentBundle.models.map(wrapModel);
4501
+ return __spreadProps(__spreadValues({}, origCurrentBundle), {
4502
+ models: wrappedModels
4251
4503
  });
4252
4504
  }
4253
4505
 
4254
4506
  // src/perf/perf-runner.ts
4255
- import { assertNever as assertNever12 } from "assert-never";
4507
+ import { assertNever as assertNever11 } from "assert-never";
4256
4508
 
4257
4509
  // src/perf/perf-stats.ts
4258
4510
  var PerfStats = class {
@@ -4289,80 +4541,110 @@ var PerfStats = class {
4289
4541
  };
4290
4542
 
4291
4543
  // src/perf/perf-runner.ts
4292
- var warmupCount = 5;
4293
- var runCount = 100;
4544
+ function runPerfWithTaskQueue(taskQueue, callbacks, options) {
4545
+ const perfRunner = new PerfRunner(taskQueue, callbacks, options);
4546
+ perfRunner.start();
4547
+ return () => {
4548
+ perfRunner.cancel();
4549
+ };
4550
+ }
4551
+ function runPerf(callbacks, options) {
4552
+ const taskQueue = TaskQueue.getInstance();
4553
+ return runPerfWithTaskQueue(taskQueue, callbacks, options);
4554
+ }
4294
4555
  var PerfRunner = class {
4295
- constructor(bundleModelL, bundleModelR, mode = "serial") {
4296
- this.bundleModelL = bundleModelL;
4297
- this.bundleModelR = bundleModelR;
4298
- this.mode = mode;
4299
- const scenarioSpec = allInputsAtPositionSpec("at-default");
4300
- this.taskQueue = new TaskQueue({
4301
- process: (request) => __async(this, null, function* () {
4302
- switch (request.kind) {
4303
- case "left": {
4304
- const result = yield bundleModelL.getDatasetsForScenario(scenarioSpec, []);
4305
- return {
4306
- runTimeL: result.modelRunTime
4307
- };
4308
- }
4309
- case "right": {
4310
- const result = yield bundleModelR.getDatasetsForScenario(scenarioSpec, []);
4311
- return {
4312
- runTimeR: result.modelRunTime
4313
- };
4314
- }
4315
- case "both": {
4316
- const [resultL, resultR] = yield Promise.all([
4317
- bundleModelL.getDatasetsForScenario(scenarioSpec, []),
4318
- bundleModelR.getDatasetsForScenario(scenarioSpec, [])
4319
- ]);
4320
- return {
4321
- runTimeL: resultL.modelRunTime,
4322
- runTimeR: resultR.modelRunTime
4323
- };
4324
- }
4325
- default:
4326
- assertNever12(request.kind);
4327
- }
4328
- })
4329
- });
4556
+ constructor(taskQueue, callbacks, options) {
4557
+ this.taskQueue = taskQueue;
4558
+ this.callbacks = callbacks;
4559
+ this.options = options;
4560
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4561
+ this.stopped = false;
4562
+ }
4563
+ cancel() {
4564
+ if (!this.stopped) {
4565
+ for (const taskKey of this.pendingTaskKeys) {
4566
+ this.taskQueue.cancelTask(taskKey);
4567
+ }
4568
+ this.stopped = true;
4569
+ }
4330
4570
  }
4331
4571
  start() {
4572
+ var _a, _b, _c, _d, _e, _f;
4332
4573
  const statsL = new PerfStats();
4333
4574
  const statsR = new PerfStats();
4334
- this.taskQueue.onIdle = (error) => {
4335
- var _a;
4336
- if (error) {
4337
- this.onError(error);
4338
- } else {
4339
- (_a = this.onComplete) == null ? void 0 : _a.call(this, statsL.toReport(), statsR.toReport());
4340
- }
4341
- };
4342
- const taskQueue = this.taskQueue;
4343
- function addTask(index, warmup, kind) {
4344
- const key = `${warmup ? "warmup-" : ""}${kind}-${index}`;
4345
- const request = {
4346
- kind
4347
- };
4348
- taskQueue.addTask(key, request, (response) => {
4349
- if (!warmup && response.runTimeL !== void 0) {
4350
- statsL.addRun(response.runTimeL);
4351
- }
4352
- if (!warmup && response.runTimeR !== void 0) {
4353
- statsR.addRun(response.runTimeR);
4354
- }
4355
- });
4575
+ const scenarioSpec = allInputsAtPositionSpec("at-default");
4576
+ const warmupCount = (_b = (_a = this.options) == null ? void 0 : _a.warmupCount) != null ? _b : 5;
4577
+ const runCount = (_d = (_c = this.options) == null ? void 0 : _c.runCount) != null ? _d : 100;
4578
+ let totalTasks = 0;
4579
+ if (((_e = this.options) == null ? void 0 : _e.mode) === "parallel") {
4580
+ totalTasks = warmupCount + runCount;
4581
+ } else {
4582
+ totalTasks = (warmupCount + runCount) * 2;
4356
4583
  }
4584
+ let tasksCompleted = 0;
4585
+ let perfTaskId = 1;
4586
+ const addTask = (warmup, kind) => {
4587
+ const task = {
4588
+ key: `perf-runner-${perfTaskId++}`,
4589
+ kind: "perf-runner",
4590
+ process: (bundleModels) => __async(this, null, function* () {
4591
+ var _a2, _b2, _c2, _d2;
4592
+ this.pendingTaskKeys.delete(task.key);
4593
+ try {
4594
+ let runTimeL;
4595
+ let runTimeR;
4596
+ switch (kind) {
4597
+ case "left": {
4598
+ const result = yield bundleModels.L.getDatasetsForScenario(scenarioSpec, []);
4599
+ runTimeL = result.modelRunTime;
4600
+ break;
4601
+ }
4602
+ case "right": {
4603
+ const result = yield bundleModels.R.getDatasetsForScenario(scenarioSpec, []);
4604
+ runTimeR = result.modelRunTime;
4605
+ break;
4606
+ }
4607
+ case "both": {
4608
+ const [resultL, resultR] = yield Promise.all([
4609
+ bundleModels.L.getDatasetsForScenario(scenarioSpec, []),
4610
+ bundleModels.R.getDatasetsForScenario(scenarioSpec, [])
4611
+ ]);
4612
+ runTimeL = resultL.modelRunTime;
4613
+ runTimeR = resultR.modelRunTime;
4614
+ break;
4615
+ }
4616
+ default:
4617
+ assertNever11(kind);
4618
+ }
4619
+ if (!warmup) {
4620
+ if (runTimeL !== void 0) {
4621
+ statsL.addRun(runTimeL);
4622
+ }
4623
+ if (runTimeR !== void 0) {
4624
+ statsR.addRun(runTimeR);
4625
+ }
4626
+ }
4627
+ tasksCompleted++;
4628
+ if (tasksCompleted === totalTasks) {
4629
+ (_b2 = (_a2 = this.callbacks).onComplete) == null ? void 0 : _b2.call(_a2, statsL.toReport(), statsR.toReport());
4630
+ }
4631
+ } catch (error) {
4632
+ (_d2 = (_c2 = this.callbacks).onError) == null ? void 0 : _d2.call(_c2, error);
4633
+ }
4634
+ })
4635
+ };
4636
+ this.taskQueue.addTask(task);
4637
+ this.pendingTaskKeys.add(task.key);
4638
+ };
4357
4639
  function addTasks(kind) {
4358
4640
  for (let i = 0; i < warmupCount; i++) {
4359
- addTask(i, true, kind);
4641
+ addTask(true, kind);
4360
4642
  }
4361
4643
  for (let i = 0; i < runCount; i++) {
4362
- addTask(i, false, kind);
4644
+ addTask(false, kind);
4363
4645
  }
4364
4646
  }
4365
- if (this.mode === "parallel") {
4647
+ if (((_f = this.options) == null ? void 0 : _f.mode) === "parallel") {
4366
4648
  addTasks("both");
4367
4649
  } else {
4368
4650
  addTasks("left");
@@ -4371,6 +4653,303 @@ var PerfRunner = class {
4371
4653
  }
4372
4654
  };
4373
4655
 
4656
+ // src/trace/trace-runner.ts
4657
+ import { assertNever as assertNever12 } from "assert-never";
4658
+ function runTraceWithTaskQueue(modelSpec, taskQueue, callbacks, options) {
4659
+ const traceRunner = new TraceRunner(taskQueue, callbacks);
4660
+ traceRunner.start(modelSpec, options);
4661
+ return () => {
4662
+ traceRunner.cancel();
4663
+ };
4664
+ }
4665
+ function runTrace(modelSpec, callbacks, options) {
4666
+ const taskQueue = TaskQueue.getInstance();
4667
+ return runTraceWithTaskQueue(modelSpec, taskQueue, callbacks, options);
4668
+ }
4669
+ var TraceRunner = class {
4670
+ constructor(taskQueue, callbacks) {
4671
+ this.taskQueue = taskQueue;
4672
+ this.callbacks = callbacks;
4673
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4674
+ this.stopped = false;
4675
+ }
4676
+ cancel() {
4677
+ if (!this.stopped) {
4678
+ for (const taskKey of this.pendingTaskKeys) {
4679
+ this.taskQueue.cancelTask(taskKey);
4680
+ }
4681
+ this.stopped = true;
4682
+ }
4683
+ }
4684
+ start(modelSpec, options) {
4685
+ const allDatasetKeys = [...modelSpec.implVars.keys()];
4686
+ const traceRequests = [];
4687
+ const batchSize = 2e3;
4688
+ for (let i = 0; i < allDatasetKeys.length; i += batchSize) {
4689
+ const datasetKeysForBatch = allDatasetKeys.slice(i, i + batchSize);
4690
+ switch (options.kind) {
4691
+ case "compare-to-bundle":
4692
+ traceRequests.push({
4693
+ kind: "compare-to-bundle",
4694
+ datasetKeys: datasetKeysForBatch,
4695
+ bundleSide0: options.bundleSide0,
4696
+ scenarioSpec0: options.scenarioSpec0,
4697
+ bundleSide1: options.bundleSide1,
4698
+ scenarioSpec1: options.scenarioSpec1
4699
+ });
4700
+ break;
4701
+ case "compare-to-ext-data":
4702
+ traceRequests.push({
4703
+ kind: "compare-to-ext-data",
4704
+ datasetKeys: datasetKeysForBatch,
4705
+ extData: options.extData,
4706
+ bundleSide: options.bundleSide,
4707
+ scenarioSpec: options.scenarioSpec
4708
+ });
4709
+ break;
4710
+ default:
4711
+ assertNever12(options);
4712
+ }
4713
+ }
4714
+ const allDatasetReports = /* @__PURE__ */ new Map();
4715
+ const taskCount = traceRequests.length;
4716
+ let tasksCompleted = 0;
4717
+ let traceTaskId = 1;
4718
+ for (const traceRequest of traceRequests) {
4719
+ const task = {
4720
+ key: `trace-runner-${traceTaskId++}`,
4721
+ kind: "trace-runner",
4722
+ process: (bundleModels) => __async(this, null, function* () {
4723
+ var _a, _b;
4724
+ this.pendingTaskKeys.delete(task.key);
4725
+ let datasetReports;
4726
+ switch (traceRequest.kind) {
4727
+ case "compare-to-bundle":
4728
+ datasetReports = yield processCompareToBundleRequest(traceRequest, bundleModels);
4729
+ break;
4730
+ case "compare-to-ext-data":
4731
+ datasetReports = yield processCompareToExtDataRequest(traceRequest, bundleModels);
4732
+ break;
4733
+ default:
4734
+ assertNever12(traceRequest);
4735
+ }
4736
+ for (const datasetReport of datasetReports) {
4737
+ allDatasetReports.set(datasetReport.datasetKey, datasetReport);
4738
+ }
4739
+ tasksCompleted++;
4740
+ if (tasksCompleted === taskCount) {
4741
+ const traceReport = {
4742
+ datasetReports: allDatasetReports
4743
+ };
4744
+ (_b = (_a = this.callbacks).onComplete) == null ? void 0 : _b.call(_a, traceReport);
4745
+ }
4746
+ })
4747
+ };
4748
+ this.taskQueue.addTask(task);
4749
+ this.pendingTaskKeys.add(task.key);
4750
+ }
4751
+ }
4752
+ };
4753
+ function processCompareToBundleRequest(request, bundleModels) {
4754
+ return __async(this, null, function* () {
4755
+ const bundleModel0 = request.bundleSide0 === "left" ? bundleModels.L : bundleModels.R;
4756
+ const bundleModel1 = request.bundleSide1 === "left" ? bundleModels.L : bundleModels.R;
4757
+ let result0;
4758
+ let result1;
4759
+ if (bundleModel1 === bundleModel0) {
4760
+ result0 = yield bundleModel0.getDatasetsForScenario(request.scenarioSpec0, request.datasetKeys);
4761
+ result1 = yield bundleModel1.getDatasetsForScenario(request.scenarioSpec1, request.datasetKeys);
4762
+ } else {
4763
+ ;
4764
+ [result0, result1] = yield Promise.all([
4765
+ bundleModel0.getDatasetsForScenario(request.scenarioSpec0, request.datasetKeys),
4766
+ bundleModel1.getDatasetsForScenario(request.scenarioSpec1, request.datasetKeys)
4767
+ ]);
4768
+ }
4769
+ const datasetReports = [];
4770
+ for (const datasetKey of request.datasetKeys) {
4771
+ const dataset0 = result0.datasetMap.get(datasetKey);
4772
+ const dataset1 = result1.datasetMap.get(datasetKey);
4773
+ const datasetReport = diffDatasets2(
4774
+ datasetKey,
4775
+ dataset0,
4776
+ dataset1,
4777
+ /*matchPrecisionOfLeft=*/
4778
+ false
4779
+ );
4780
+ datasetReports.push(datasetReport);
4781
+ }
4782
+ return datasetReports;
4783
+ });
4784
+ }
4785
+ function processCompareToExtDataRequest(request, bundleModels) {
4786
+ return __async(this, null, function* () {
4787
+ const bundleModel = request.bundleSide === "left" ? bundleModels.L : bundleModels.R;
4788
+ const resultR = yield bundleModel.getDatasetsForScenario(request.scenarioSpec, request.datasetKeys);
4789
+ const datasetReports = [];
4790
+ for (const datasetKey of request.datasetKeys) {
4791
+ let datasetL = request.extData.get(datasetKey);
4792
+ if (datasetL === void 0) {
4793
+ const datasetKeyParts = datasetKey.split("[");
4794
+ if (datasetKeyParts.length === 2) {
4795
+ const baseKey = datasetKeyParts[0];
4796
+ const keySubParts = datasetKeyParts[1].replace("]", "");
4797
+ const keySubIds = keySubParts.split(",");
4798
+ const subIdPermutations = permutationsOf(keySubIds);
4799
+ for (const subIds of subIdPermutations) {
4800
+ const datDatasetKey = `${baseKey}[${subIds.join(",")}]`;
4801
+ datasetL = request.extData.get(datDatasetKey);
4802
+ if (datasetL !== void 0) {
4803
+ break;
4804
+ }
4805
+ }
4806
+ }
4807
+ if (datasetL === void 0) {
4808
+ console.warn(`WARNING: Failed to find data in dat file for key=${datasetKey}`);
4809
+ }
4810
+ }
4811
+ const datasetR = resultR.datasetMap.get(datasetKey);
4812
+ const datasetReport = diffDatasets2(
4813
+ datasetKey,
4814
+ datasetL,
4815
+ datasetR,
4816
+ /*matchPrecisionOfLeft=*/
4817
+ true
4818
+ );
4819
+ datasetReports.push(datasetReport);
4820
+ }
4821
+ return datasetReports;
4822
+ });
4823
+ }
4824
+ function diffDatasets2(datasetKey, datasetL, datasetR, matchPrecisionOfLeft) {
4825
+ const points = /* @__PURE__ */ new Map();
4826
+ let minValueL = Number.MAX_VALUE;
4827
+ let maxValueL = Number.MIN_VALUE;
4828
+ let minValueR = Number.MAX_VALUE;
4829
+ let maxValueR = Number.MIN_VALUE;
4830
+ let minValue = Number.MAX_VALUE;
4831
+ let maxValue = Number.MIN_VALUE;
4832
+ let minRawDiff = Number.MAX_VALUE;
4833
+ let maxRawDiff = -1;
4834
+ let maxDiffPoint;
4835
+ let diffCount = 0;
4836
+ let totalRawDiff = 0;
4837
+ if (datasetL && datasetR) {
4838
+ const times = /* @__PURE__ */ new Set([...datasetL.keys(), ...datasetR.keys()]);
4839
+ for (const t of times) {
4840
+ const valueL = datasetL.get(t);
4841
+ if (valueL !== void 0) {
4842
+ if (valueL < minValueL) minValueL = valueL;
4843
+ if (valueL > maxValueL) maxValueL = valueL;
4844
+ if (valueL < minValue) minValue = valueL;
4845
+ if (valueL > maxValue) maxValue = valueL;
4846
+ }
4847
+ let valueR;
4848
+ const rawValueR = datasetR.get(t);
4849
+ if (rawValueR !== void 0) {
4850
+ if (matchPrecisionOfLeft && valueL !== void 0) {
4851
+ valueR = matchPrecision(rawValueR, valueL);
4852
+ } else {
4853
+ valueR = rawValueR;
4854
+ }
4855
+ if (valueR < minValueR) minValueR = valueR;
4856
+ if (valueR > maxValueR) maxValueR = valueR;
4857
+ if (valueR < minValue) minValue = valueR;
4858
+ if (valueR > maxValue) maxValue = valueR;
4859
+ }
4860
+ if (valueL === void 0 || valueR === void 0) {
4861
+ continue;
4862
+ }
4863
+ const point = {
4864
+ time: t,
4865
+ valueL,
4866
+ valueR
4867
+ };
4868
+ points.set(t, point);
4869
+ const rawDiff = Math.abs(valueR - valueL);
4870
+ if (rawDiff < minRawDiff) {
4871
+ minRawDiff = rawDiff;
4872
+ }
4873
+ if (rawDiff > maxRawDiff) {
4874
+ maxRawDiff = rawDiff;
4875
+ maxDiffPoint = point;
4876
+ }
4877
+ diffCount++;
4878
+ totalRawDiff += rawDiff;
4879
+ }
4880
+ }
4881
+ function pct(x) {
4882
+ return x * 100;
4883
+ }
4884
+ let minDiff;
4885
+ let maxDiff;
4886
+ let avgDiff;
4887
+ if (minValueL === maxValueL && minValueR === maxValueR) {
4888
+ const diff = pct(maxValueL !== 0 ? Math.abs((maxValueR - maxValueL) / maxValueL) : 1);
4889
+ minDiff = diff;
4890
+ maxDiff = diff;
4891
+ avgDiff = diff;
4892
+ } else {
4893
+ const spread = maxValue - minValue;
4894
+ minDiff = pct(spread > 0 ? minRawDiff / spread : 0);
4895
+ maxDiff = pct(spread > 0 ? maxRawDiff / spread : 0);
4896
+ const avgRawDiff = totalRawDiff / diffCount;
4897
+ avgDiff = pct(spread > 0 ? avgRawDiff / spread : 0);
4898
+ }
4899
+ let validity;
4900
+ if (datasetL && datasetR) {
4901
+ validity = "both";
4902
+ } else if (datasetL) {
4903
+ validity = "left-only";
4904
+ } else if (datasetR) {
4905
+ validity = "right-only";
4906
+ } else {
4907
+ validity = "neither";
4908
+ }
4909
+ return {
4910
+ datasetKey,
4911
+ validity,
4912
+ points,
4913
+ minValue,
4914
+ maxValue,
4915
+ avgDiff,
4916
+ minDiff,
4917
+ maxDiff,
4918
+ maxDiffPoint
4919
+ };
4920
+ }
4921
+ function matchPrecision(x, baseline) {
4922
+ const s = baseline.toString();
4923
+ if (s.includes("e")) {
4924
+ return x;
4925
+ }
4926
+ const parts = s.split(".");
4927
+ if (parts.length < 2) {
4928
+ return x;
4929
+ }
4930
+ const sigDigits = parts[1].replace(/0+$/, "").length;
4931
+ if (sigDigits > 21) {
4932
+ return x;
4933
+ }
4934
+ return parseFloat(x.toFixed(sigDigits));
4935
+ }
4936
+ function permutationsOf(inputArr) {
4937
+ const result = [];
4938
+ const permute = (arr, m = []) => {
4939
+ if (arr.length === 0) {
4940
+ result.push(m);
4941
+ } else {
4942
+ for (let i = 0; i < arr.length; i++) {
4943
+ const curr = arr.slice();
4944
+ const next = curr.splice(i, 1);
4945
+ permute(curr.slice(), m.concat(next));
4946
+ }
4947
+ }
4948
+ };
4949
+ permute(inputArr);
4950
+ return result;
4951
+ }
4952
+
4374
4953
  // src/data/data-planner.ts
4375
4954
  var DataPlanner = class {
4376
4955
  /**
@@ -4557,10 +5136,10 @@ function scenarioPairUid(scenarioSpecL, scenarioSpecR) {
4557
5136
  }
4558
5137
 
4559
5138
  // src/check/check-runner.ts
4560
- function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios) {
4561
- const modelSpec = checkConfig.bundle.model.modelSpec;
5139
+ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, skipChecks) {
5140
+ const modelSpec = checkConfig.bundle.modelSpec;
4562
5141
  const checkPlanner = new CheckPlanner(modelSpec);
4563
- checkPlanner.addAllChecks(checkSpec, simplifyScenarios);
5142
+ checkPlanner.addAllChecks(checkSpec, skipChecks);
4564
5143
  const checkPlan = checkPlanner.buildPlan();
4565
5144
  const refDatasets = /* @__PURE__ */ new Map();
4566
5145
  for (const [dataRefKey, dataRef] of checkPlan.dataRefs.entries()) {
@@ -4573,11 +5152,15 @@ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplify
4573
5152
  }
4574
5153
  const checkResults = /* @__PURE__ */ new Map();
4575
5154
  for (const [checkKey, checkTask] of checkPlan.tasks.entries()) {
4576
- dataPlanner.addRequest(void 0, checkTask.scenario.spec, checkTask.dataset.datasetKey, (datasets) => {
4577
- const dataset = datasets.datasetR;
4578
- const checkResult = runCheck(checkTask, dataset, refDatasets);
4579
- checkResults.set(checkKey, checkResult);
4580
- });
5155
+ if (checkTask.skip === true) {
5156
+ checkResults.set(checkKey, { status: "skipped" });
5157
+ } else {
5158
+ dataPlanner.addRequest(void 0, checkTask.scenario.spec, checkTask.dataset.datasetKey, (datasets) => {
5159
+ const dataset = datasets.datasetR;
5160
+ const checkResult = runCheck(checkTask, dataset, refDatasets);
5161
+ checkResults.set(checkKey, checkResult);
5162
+ });
5163
+ }
4581
5164
  }
4582
5165
  return () => {
4583
5166
  return buildCheckReport(checkPlan, checkResults);
@@ -4642,21 +5225,73 @@ function runCheck(checkTask, dataset, refDatasets) {
4642
5225
  }
4643
5226
 
4644
5227
  // src/comparison/run/comparison-runner.ts
4645
- function runComparisons(comparisonConfig, dataPlanner) {
5228
+ function runComparisons(comparisonConfig, dataPlanner, skipScenarios) {
5229
+ function skipScenarioKey(title, subtitle) {
5230
+ let key = title.toLowerCase();
5231
+ if (subtitle) {
5232
+ key += ` :: ${subtitle.toLowerCase()}`;
5233
+ }
5234
+ return key;
5235
+ }
5236
+ const skipScenariosSet = new Set(skipScenarios.map((scenario) => skipScenarioKey(scenario.title, scenario.subtitle)));
5237
+ const allScenarios = [...comparisonConfig.scenarios.getAllScenarios()];
5238
+ let baselineScenario;
5239
+ const baselineScenarioIndex = allScenarios.findIndex((scenario) => {
5240
+ const settings = scenario.settings;
5241
+ return settings.kind === "all-inputs-settings" && settings.position === "at-default";
5242
+ });
5243
+ if (baselineScenarioIndex !== -1) {
5244
+ baselineScenario = allScenarios.splice(baselineScenarioIndex, 1)[0];
5245
+ }
4646
5246
  const testReports = [];
4647
- for (const scenario of comparisonConfig.scenarios.getAllScenarios()) {
5247
+ const baselineDiffReports = /* @__PURE__ */ new Map();
5248
+ function runComparisonsForScenario(scenario, isBaseline) {
4648
5249
  const datasetKeys = comparisonConfig.datasets.getDatasetKeysForScenario(scenario);
4649
- for (const datasetKey of datasetKeys) {
4650
- dataPlanner.addRequest(scenario.specL, scenario.specR, datasetKey, (datasets) => {
4651
- const diffReport = diffDatasets(datasets.datasetL, datasets.datasetR);
5250
+ const shouldSkip = skipScenariosSet.has(skipScenarioKey(scenario.title, scenario.subtitle));
5251
+ if (shouldSkip) {
5252
+ for (const datasetKey of datasetKeys) {
4652
5253
  testReports.push({
4653
5254
  scenarioKey: scenario.key,
4654
5255
  datasetKey,
4655
- diffReport
5256
+ diffReport: void 0
4656
5257
  });
5258
+ }
5259
+ return;
5260
+ }
5261
+ for (const datasetKey of datasetKeys) {
5262
+ dataPlanner.addRequest(scenario.specL, scenario.specR, datasetKey, (datasets) => {
5263
+ const diffReport = diffDatasets(datasets.datasetL, datasets.datasetR);
5264
+ if (isBaseline) {
5265
+ baselineDiffReports.set(datasetKey, diffReport);
5266
+ testReports.push({
5267
+ scenarioKey: scenario.key,
5268
+ datasetKey,
5269
+ diffReport
5270
+ });
5271
+ } else {
5272
+ const baselineDiffReport = baselineDiffReports.get(datasetKey);
5273
+ testReports.push({
5274
+ scenarioKey: scenario.key,
5275
+ datasetKey,
5276
+ diffReport,
5277
+ baselineDiffReport
5278
+ });
5279
+ }
4657
5280
  });
4658
5281
  }
4659
5282
  }
5283
+ runComparisonsForScenario(
5284
+ baselineScenario,
5285
+ /*isBaseline=*/
5286
+ true
5287
+ );
5288
+ for (const scenario of allScenarios) {
5289
+ runComparisonsForScenario(
5290
+ scenario,
5291
+ /*isBaseline=*/
5292
+ false
5293
+ );
5294
+ }
4660
5295
  return () => {
4661
5296
  return testReports;
4662
5297
  };
@@ -4664,74 +5299,52 @@ function runComparisons(comparisonConfig, dataPlanner) {
4664
5299
 
4665
5300
  // src/suite/suite-runner.ts
4666
5301
  var SuiteRunner = class {
4667
- constructor(config, callbacks) {
5302
+ constructor(config, taskQueue, callbacks) {
4668
5303
  this.config = config;
5304
+ this.taskQueue = taskQueue;
4669
5305
  this.callbacks = callbacks;
4670
5306
  this.perfStatsL = new PerfStats();
4671
5307
  this.perfStatsR = new PerfStats();
5308
+ this.pendingTaskKeys = /* @__PURE__ */ new Set();
4672
5309
  this.stopped = false;
4673
- this.taskQueue = new TaskQueue({
4674
- process: (request) => {
4675
- return this.processRequest(request);
4676
- }
4677
- });
4678
5310
  }
4679
5311
  cancel() {
4680
5312
  if (!this.stopped) {
5313
+ for (const taskKey of this.pendingTaskKeys) {
5314
+ this.taskQueue.cancelTask(taskKey);
5315
+ }
4681
5316
  this.stopped = true;
4682
- this.taskQueue.shutdown();
4683
5317
  }
4684
5318
  }
4685
5319
  start(options) {
4686
5320
  var _a, _b, _c, _d, _e, _f, _g, _h;
4687
5321
  (_b = (_a = this.callbacks).onProgress) == null ? void 0 : _b.call(_a, 0);
4688
- const modelSpec = this.config.check.bundle.model.modelSpec;
4689
- const dataPlanner = new DataPlanner(modelSpec.outputVars.size);
4690
- const refDataPlanner = new DataPlanner(modelSpec.outputVars.size);
5322
+ const modelSpecR = this.config.check.bundle.modelSpec;
5323
+ const dataPlanner = new DataPlanner(modelSpecR.outputVars.size);
5324
+ const refDataPlanner = new DataPlanner(modelSpecR.outputVars.size);
4691
5325
  const checkSpecResult = parseTestYaml(this.config.check.tests);
4692
5326
  if (checkSpecResult.isErr()) {
4693
5327
  (_d = (_c = this.callbacks).onError) == null ? void 0 : _d.call(_c, checkSpecResult.error);
4694
5328
  return;
4695
5329
  }
4696
5330
  const checkSpec = checkSpecResult.value;
4697
- const simplifyScenarios = (options == null ? void 0 : options.simplifyScenarios) === true;
4698
- const buildCheckReport2 = runChecks(this.config.check, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios);
5331
+ const skipChecks = (options == null ? void 0 : options.skipChecks) || [];
5332
+ const buildCheckReport2 = runChecks(this.config.check, checkSpec, dataPlanner, refDataPlanner, skipChecks);
4699
5333
  let buildComparisonTestReports;
4700
5334
  if (this.config.comparison) {
4701
- buildComparisonTestReports = runComparisons(this.config.comparison, dataPlanner);
5335
+ const skipScenarios = (options == null ? void 0 : options.skipComparisonScenarios) || [];
5336
+ buildComparisonTestReports = runComparisons(this.config.comparison, dataPlanner, skipScenarios);
4702
5337
  }
4703
- this.taskQueue.onIdle = (error) => {
4704
- var _a2, _b2, _c2, _d2;
4705
- if (this.stopped) {
4706
- return;
4707
- }
4708
- if (error) {
4709
- (_b2 = (_a2 = this.callbacks).onError) == null ? void 0 : _b2.call(_a2, error);
4710
- } else {
4711
- const checkReport = buildCheckReport2();
4712
- let comparisonReport;
4713
- if (this.config.comparison) {
4714
- comparisonReport = {
4715
- testReports: buildComparisonTestReports(),
4716
- perfReportL: this.perfStatsL.toReport(),
4717
- perfReportR: this.perfStatsR.toReport()
4718
- };
4719
- }
4720
- (_d2 = (_c2 = this.callbacks).onComplete) == null ? void 0 : _d2.call(_c2, {
4721
- checkReport,
4722
- comparisonReport
4723
- });
4724
- }
4725
- };
4726
5338
  const refDataPlan = refDataPlanner.buildPlan();
4727
5339
  const dataPlan = dataPlanner.buildPlan();
4728
5340
  const dataRequests = [...refDataPlan.requests, ...dataPlan.requests];
4729
5341
  const taskCount = dataRequests.length;
4730
5342
  if (taskCount === 0) {
5343
+ const checkReport = buildCheckReport2();
4731
5344
  let comparisonReport;
4732
5345
  if (this.config.comparison) {
4733
5346
  comparisonReport = {
4734
- testReports: [],
5347
+ testReports: buildComparisonTestReports(),
4735
5348
  perfReportL: this.perfStatsL.toReport(),
4736
5349
  perfReportR: this.perfStatsR.toReport()
4737
5350
  };
@@ -4739,26 +5352,54 @@ var SuiteRunner = class {
4739
5352
  this.cancel();
4740
5353
  (_f = (_e = this.callbacks).onProgress) == null ? void 0 : _f.call(_e, 1);
4741
5354
  (_h = (_g = this.callbacks).onComplete) == null ? void 0 : _h.call(_g, {
4742
- checkReport: {
4743
- groups: []
4744
- },
5355
+ checkReport,
4745
5356
  comparisonReport
4746
5357
  });
4747
5358
  return;
4748
5359
  }
5360
+ const buildReport = (error) => {
5361
+ var _a2, _b2, _c2, _d2;
5362
+ if (error) {
5363
+ (_b2 = (_a2 = this.callbacks).onError) == null ? void 0 : _b2.call(_a2, error);
5364
+ } else {
5365
+ const checkReport = buildCheckReport2();
5366
+ let comparisonReport;
5367
+ if (this.config.comparison) {
5368
+ comparisonReport = {
5369
+ testReports: buildComparisonTestReports(),
5370
+ perfReportL: this.perfStatsL.toReport(),
5371
+ perfReportR: this.perfStatsR.toReport()
5372
+ };
5373
+ }
5374
+ (_d2 = (_c2 = this.callbacks).onComplete) == null ? void 0 : _d2.call(_c2, {
5375
+ checkReport,
5376
+ comparisonReport
5377
+ });
5378
+ }
5379
+ };
4749
5380
  let tasksCompleted = 0;
4750
5381
  let dataTaskId = 1;
4751
5382
  for (const dataRequest of dataRequests) {
4752
- this.taskQueue.addTask(`data${dataTaskId++}`, dataRequest, () => {
4753
- var _a2, _b2;
4754
- tasksCompleted++;
4755
- (_b2 = (_a2 = this.callbacks).onProgress) == null ? void 0 : _b2.call(_a2, tasksCompleted / taskCount);
4756
- });
5383
+ const task = {
5384
+ key: `suite-runner-${dataTaskId++}`,
5385
+ kind: "suite-runner",
5386
+ process: (bundleModels) => __async(this, null, function* () {
5387
+ var _a2, _b2;
5388
+ this.pendingTaskKeys.delete(task.key);
5389
+ yield this.processRequest(dataRequest, bundleModels);
5390
+ tasksCompleted++;
5391
+ (_b2 = (_a2 = this.callbacks).onProgress) == null ? void 0 : _b2.call(_a2, tasksCompleted / taskCount);
5392
+ if (tasksCompleted === taskCount) {
5393
+ buildReport();
5394
+ }
5395
+ })
5396
+ };
5397
+ this.taskQueue.addTask(task);
5398
+ this.pendingTaskKeys.add(task.key);
4757
5399
  }
4758
5400
  }
4759
- processRequest(request) {
5401
+ processRequest(request, bundleModels) {
4760
5402
  return __async(this, null, function* () {
4761
- var _a, _b;
4762
5403
  const datasetKeySet = /* @__PURE__ */ new Set();
4763
5404
  for (const dataTask of request.dataTasks) {
4764
5405
  datasetKeySet.add(dataTask.datasetKey);
@@ -4773,8 +5414,8 @@ var SuiteRunner = class {
4773
5414
  }
4774
5415
  });
4775
5416
  }
4776
- const bundleModelL = (_a = this.config.comparison) == null ? void 0 : _a.bundleL.model;
4777
- const bundleModelR = ((_b = this.config.comparison) == null ? void 0 : _b.bundleR.model) || this.config.check.bundle.model;
5417
+ const bundleModelL = bundleModels.L;
5418
+ const bundleModelR = bundleModels.R;
4778
5419
  const [datasetsResultL, datasetsResultR] = yield Promise.all([
4779
5420
  getDatasets(bundleModelL, request.scenarioSpecL),
4780
5421
  getDatasets(bundleModelR, request.scenarioSpecR)
@@ -4798,22 +5439,29 @@ var SuiteRunner = class {
4798
5439
  });
4799
5440
  }
4800
5441
  };
4801
- function runSuite(config, callbacks, options) {
4802
- const suiteRunner = new SuiteRunner(config, callbacks);
5442
+ function runSuiteWithTaskQueue(config, taskQueue, callbacks, options) {
5443
+ const suiteRunner = new SuiteRunner(config, taskQueue, callbacks);
4803
5444
  suiteRunner.start(options);
4804
5445
  return () => {
4805
5446
  suiteRunner.cancel();
4806
5447
  };
4807
5448
  }
5449
+ function runSuite(config, callbacks, options) {
5450
+ const taskQueue = TaskQueue.getInstance();
5451
+ return runSuiteWithTaskQueue(config, taskQueue, callbacks, options);
5452
+ }
4808
5453
 
4809
5454
  // src/suite/suite-reporting.ts
4810
- function suiteSummaryFromReport(suiteReport) {
5455
+ function suiteSummaryFromReport(suiteReport, elapsedMillis) {
4811
5456
  const checkSummary = checkSummaryFromReport(suiteReport.checkReport);
4812
5457
  let comparisonSummary;
4813
5458
  if (suiteReport.comparisonReport) {
4814
5459
  comparisonSummary = comparisonSummaryFromReport(suiteReport.comparisonReport);
4815
5460
  }
5461
+ const date = (/* @__PURE__ */ new Date()).toISOString();
4816
5462
  return {
5463
+ date,
5464
+ elapsed: elapsedMillis,
4817
5465
  checkSummary,
4818
5466
  comparisonSummary
4819
5467
  };
@@ -4821,20 +5469,27 @@ function suiteSummaryFromReport(suiteReport) {
4821
5469
  export {
4822
5470
  CheckDataCoordinator,
4823
5471
  ComparisonDataCoordinator,
4824
- PerfRunner,
4825
5472
  PerfStats,
4826
5473
  categorizeComparisonTestSummaries,
4827
5474
  checkReportFromSummary,
4828
5475
  checkSummaryFromReport,
4829
5476
  comparisonSummaryFromReport,
5477
+ createCheckDataCoordinator,
5478
+ createCheckDataCoordinatorForTests,
5479
+ createComparisonDataCoordinator,
4830
5480
  createConfig,
4831
5481
  datasetMessage,
5482
+ decodeImplVars,
4832
5483
  diffDatasets,
4833
5484
  diffGraphs,
5485
+ encodeImplVars,
4834
5486
  getScoresForTestSummaries,
4835
5487
  predicateMessage,
5488
+ runPerf,
4836
5489
  runSuite,
5490
+ runTrace,
4837
5491
  scenarioMessage,
4838
- suiteSummaryFromReport
5492
+ suiteSummaryFromReport,
5493
+ testSummaryFromReport
4839
5494
  };
4840
5495
  //# sourceMappingURL=index.js.map