@nshiab/simple-data-analysis-core 2.1.5 → 2.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.
Files changed (57) hide show
  1. package/esm/class/SimpleTable.d.ts +254 -27
  2. package/esm/class/SimpleTable.d.ts.map +1 -1
  3. package/esm/class/SimpleTable.js +248 -27
  4. package/esm/helpers/graphRouteResultSchema.d.ts +1 -1
  5. package/esm/helpers/graphRouteResultSchema.d.ts.map +1 -1
  6. package/esm/helpers/graphRouteResultSchema.js +3 -2
  7. package/esm/helpers/graphRouteResultSelect.d.ts +1 -1
  8. package/esm/helpers/graphRouteResultSelect.d.ts.map +1 -1
  9. package/esm/helpers/graphRouteResultSelect.js +4 -1
  10. package/esm/helpers/prepareGraphMetricOptions.d.ts +14 -0
  11. package/esm/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
  12. package/esm/helpers/prepareGraphMetricOptions.js +40 -0
  13. package/esm/helpers/prepareGraphTemporalSql.d.ts +3 -0
  14. package/esm/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
  15. package/esm/helpers/prepareGraphTemporalSql.js +7 -0
  16. package/esm/methods/distances.d.ts +2 -0
  17. package/esm/methods/distances.d.ts.map +1 -1
  18. package/esm/methods/distances.js +98 -6
  19. package/esm/methods/findCycles.d.ts +1 -0
  20. package/esm/methods/findCycles.d.ts.map +1 -1
  21. package/esm/methods/findCycles.js +22 -15
  22. package/esm/methods/paths.d.ts +1 -0
  23. package/esm/methods/paths.d.ts.map +1 -1
  24. package/esm/methods/paths.js +21 -14
  25. package/esm/methods/shortestPath.d.ts +2 -0
  26. package/esm/methods/shortestPath.d.ts.map +1 -1
  27. package/esm/methods/shortestPath.js +125 -20
  28. package/llm.md +284 -58
  29. package/llms.txt +1 -1
  30. package/package.json +1 -1
  31. package/script/class/SimpleTable.d.ts +254 -27
  32. package/script/class/SimpleTable.d.ts.map +1 -1
  33. package/script/class/SimpleTable.js +248 -27
  34. package/script/helpers/graphRouteResultSchema.d.ts +1 -1
  35. package/script/helpers/graphRouteResultSchema.d.ts.map +1 -1
  36. package/script/helpers/graphRouteResultSchema.js +3 -2
  37. package/script/helpers/graphRouteResultSelect.d.ts +1 -1
  38. package/script/helpers/graphRouteResultSelect.d.ts.map +1 -1
  39. package/script/helpers/graphRouteResultSelect.js +4 -1
  40. package/script/helpers/prepareGraphMetricOptions.d.ts +14 -0
  41. package/script/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
  42. package/script/helpers/prepareGraphMetricOptions.js +43 -0
  43. package/script/helpers/prepareGraphTemporalSql.d.ts +3 -0
  44. package/script/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
  45. package/script/helpers/prepareGraphTemporalSql.js +7 -0
  46. package/script/methods/distances.d.ts +2 -0
  47. package/script/methods/distances.d.ts.map +1 -1
  48. package/script/methods/distances.js +98 -6
  49. package/script/methods/findCycles.d.ts +1 -0
  50. package/script/methods/findCycles.d.ts.map +1 -1
  51. package/script/methods/findCycles.js +22 -15
  52. package/script/methods/paths.d.ts +1 -0
  53. package/script/methods/paths.d.ts.map +1 -1
  54. package/script/methods/paths.js +21 -14
  55. package/script/methods/shortestPath.d.ts +2 -0
  56. package/script/methods/shortestPath.d.ts.map +1 -1
  57. package/script/methods/shortestPath.js +125 -20
@@ -1,4 +1,5 @@
1
1
  import buildGraphTemporalCostStateSql from "../helpers/buildGraphTemporalCostStateSql.js";
2
+ import prepareGraphMetricOptions from "../helpers/prepareGraphMetricOptions.js";
2
3
  import { prepareGraphRouteEndpoints, prepareGraphRouteSql, validateGraphRouteInputs, } from "../helpers/prepareGraphRouteSql.js";
3
4
  import graphRouteResultSchema from "../helpers/graphRouteResultSchema.js";
4
5
  import graphRouteResultSelect from "../helpers/graphRouteResultSelect.js";
@@ -31,6 +32,7 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
31
32
  typeof options.outputTable !== "boolean") {
32
33
  throw new TypeError("shortestPath() options.outputTable must be a string or boolean.");
33
34
  }
35
+ const metrics = prepareGraphMetricOptions(options, "shortestPath()", true);
34
36
  const endpoints = prepareGraphRouteEndpoints(start, end, "shortestPath()");
35
37
  const direction = options.direction ?? "outgoing";
36
38
  const temporalOptions = prepareGraphTemporalOptions(options, direction, "shortestPath()");
@@ -59,22 +61,22 @@ export default function shortestPath(simpleTable, sourceColumn, targetColumn, ed
59
61
  ? endpoints.values.values
60
62
  : [temporal.gapParameter, ...endpoints.values.values];
61
63
  },
62
- buildSelect: (input, schema) => shortestPathSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions),
64
+ buildSelect: (input, schema) => shortestPathSelect(input, schema, sourceColumn, targetColumn, edgeId, endpoints, direction, options.weight, temporalOptions, metrics),
63
65
  outputSchema: (schema) => {
64
66
  const validated = validateGraphRouteInputs(schema, sourceColumn, targetColumn, edgeId, endpoints, options.weight, "shortestPath()");
65
67
  if (temporalOptions !== undefined) {
66
68
  prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
67
69
  }
68
- return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()");
70
+ return graphRouteResultSchema(schema, validated.distanceType, "shortestPath()", metrics.elapsedTime);
69
71
  },
70
72
  });
71
73
  }
72
- function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions) {
74
+ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoints, direction, weight, temporalOptions, metrics) {
73
75
  const route = prepareGraphRouteSql(input, schema, source, target, edgeId, routeEndpoints, weight, "shortestPath()");
74
76
  const prepared = route.traversal;
75
77
  if (temporalOptions !== undefined) {
76
78
  const temporal = prepareGraphTemporalSql(schema, temporalOptions, "shortestPath()");
77
- return temporalShortestPathSelect(route, direction, temporal);
79
+ return temporalShortestPathSelect(route, direction, temporal, metrics);
78
80
  }
79
81
  const distanceType = route.distanceType;
80
82
  // Exact shortest routes have optimal prefixes. Floating addition can erase
@@ -211,7 +213,7 @@ function shortestPathSelect(input, schema, source, target, edgeId, routeEndpoint
211
213
  )
212
214
  ${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
213
215
  }
214
- function temporalShortestPathSelect(route, direction, temporal) {
216
+ function temporalShortestPathSelect(route, direction, temporal, metrics) {
215
217
  const prepared = route.traversal;
216
218
  const q = quoteIdentifier;
217
219
  const startsSelect = `SELECT ${q("start")},
@@ -219,10 +221,12 @@ function temporalShortestPathSelect(route, direction, temporal) {
219
221
  FROM (SELECT ${route.endpointValue} AS ${q("start")}) AS ${q("values")}`;
220
222
  // Non-negative weights and no maximum gap let cycles be removed from walks.
221
223
  // Their optimum bounds simple routes; retain separate histories for all ties.
222
- const costs = buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, route.distanceType, route.edgeWeight, [
223
- `${route.typedEdgeId} AS ${q("__edge_id")}`,
224
- `${route.edgeKey} AS ${q("__edge_key")}`,
225
- ]);
224
+ const costs = metrics.minimize === "elapsedTime"
225
+ ? buildElapsedShortestPathCostSql(route, startsSelect, direction, temporal)
226
+ : buildGraphTemporalCostStateSql(prepared, startsSelect, direction, temporal, route.distanceType, route.edgeWeight, [
227
+ `${route.typedEdgeId} AS ${q("__edge_id")}`,
228
+ `${route.edgeKey} AS ${q("__edge_key")}`,
229
+ ]);
226
230
  const relations = prepared.relationNames([
227
231
  "graph_route_endpoints",
228
232
  "graph_nodes",
@@ -248,12 +252,27 @@ function temporalShortestPathSelect(route, direction, temporal) {
248
252
  const routeDistance = `${q("routes")}.${q("distance")}`;
249
253
  const candidateDistance = `CAST(${routeDistance} + ${edgeCost} AS ${route.distanceType})`;
250
254
  const gap = `${q("settings")}.${q("__gap")}`;
251
- const step = (distance) => `struct_pack(
255
+ const firstAnchor = temporal.journeyAnchor("edges", direction);
256
+ const routeAnchor = `${q("routes")}.${q("__journey_anchor")}`;
257
+ const firstElapsed = temporal.journeyElapsed("edges", firstAnchor, direction);
258
+ const candidateElapsed = temporal.journeyElapsed("edges", routeAnchor, direction);
259
+ const firstMetric = metrics.minimize === "elapsedTime"
260
+ ? firstElapsed
261
+ : edgeCost;
262
+ const candidateMetric = metrics.minimize === "elapsedTime"
263
+ ? candidateElapsed
264
+ : candidateDistance;
265
+ const routeMetric = metrics.minimize === "elapsedTime"
266
+ ? `${q("routes")}.${q("__elapsed")}`
267
+ : routeDistance;
268
+ const step = (distance, elapsed) => `struct_pack(
252
269
  ${q("edgeId")} := ${q("edges")}.${q("__edge_id")},
253
270
  ${q("source")} := ${q("edges")}.${q("__from")},
254
271
  ${q("target")} := ${edgeTo},
255
272
  ${q("weight")} := ${edgeCost},
256
- ${q("distance")} := ${distance}
273
+ ${q("distance")} := ${distance}${metrics.elapsedTime
274
+ ? `, ${q("elapsedTimeMs")} := ${temporal.elapsedMilliseconds(elapsed)}`
275
+ : ""}
257
276
  )`;
258
277
  return `${costs.withClause}, ${endpointRelation}(
259
278
  ${q("start")}, ${q("end")}, ${q("__start_key")}, ${q("__end_key")}
@@ -285,37 +304,37 @@ function temporalShortestPathSelect(route, direction, temporal) {
285
304
  ), ${routesRelation}(
286
305
  ${q("node")}, ${q("__node_key")}, ${q("__visited")},
287
306
  ${q("__edge_keys")}, ${q("steps")}, ${q("distance")},
288
- ${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}
307
+ ${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")}${metrics.elapsedTime ? `, ${q("__journey_anchor")}, ${q("__elapsed")}` : ""}
289
308
  ) AS (
290
309
  SELECT ${edgeTo}, ${edgeToKey},
291
310
  [${q("__start_key")}, ${edgeToKey}], [${edgeKey}],
292
- [${step(edgeCost)}], ${edgeCost},
311
+ [${step(edgeCost, firstElapsed)}], ${edgeCost},
293
312
  ${q("edges")}.${q("__event_id")},
294
313
  ${q("edges")}.${q("__event_start")},
295
- ${q("edges")}.${q("__event_end")}
314
+ ${q("edges")}.${q("__event_end")}${metrics.elapsedTime ? `, ${firstAnchor}, ${firstElapsed}` : ""}
296
315
  FROM ${endpointRelation}
297
316
  INNER JOIN ${edgesRelation} AS ${q("edges")}
298
317
  ON ${q("__start_key")} = ${edgeFromKey}
299
318
  INNER JOIN ${toEndRelation} AS ${q("can_reach_end")}
300
319
  ON ${edgeToKey} = ${q("can_reach_end")}.${q("__key")}
301
320
  INNER JOIN ${bestTotalRelation} AS ${q("best_total")}
302
- ON ${edgeCost} <= ${q("best_total")}.${q("distance")}
321
+ ON ${firstMetric} <= ${q("best_total")}.${q("distance")}
303
322
  WHERE ${q("__start_key")} <> ${edgeToKey}
304
323
  UNION ALL
305
324
  SELECT ${edgeTo}, ${edgeToKey},
306
325
  list_append(${q("routes")}.${q("__visited")}, ${edgeToKey}),
307
326
  list_append(${q("routes")}.${q("__edge_keys")}, ${edgeKey}),
308
- list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance)}),
327
+ list_append(${q("routes")}.${q("steps")}, ${step(candidateDistance, candidateElapsed)}),
309
328
  ${candidateDistance}, ${q("edges")}.${q("__event_id")},
310
329
  ${q("edges")}.${q("__event_start")},
311
- ${q("edges")}.${q("__event_end")}
330
+ ${q("edges")}.${q("__event_end")}${metrics.elapsedTime ? `, ${routeAnchor}, ${candidateElapsed}` : ""}
312
331
  FROM ${routesRelation} AS ${q("routes")}
313
332
  INNER JOIN ${edgesRelation} AS ${q("edges")}
314
333
  ON ${q("routes")}.${q("__node_key")} = ${edgeFromKey}
315
334
  INNER JOIN ${toEndRelation} AS ${q("can_reach_end")}
316
335
  ON ${edgeToKey} = ${q("can_reach_end")}.${q("__key")}
317
336
  INNER JOIN ${bestTotalRelation} AS ${q("best_total")}
318
- ON ${candidateDistance} <= ${q("best_total")}.${q("distance")}
337
+ ON ${candidateMetric} <= ${q("best_total")}.${q("distance")}
319
338
  CROSS JOIN ${endpointRelation}
320
339
  CROSS JOIN ${costs.settingsRelation} AS ${q("settings")}
321
340
  WHERE ${q("routes")}.${q("__node_key")} <> ${q("__end_key")}
@@ -327,11 +346,97 @@ function temporalShortestPathSelect(route, direction, temporal) {
327
346
  CROSS JOIN ${endpointRelation}
328
347
  CROSS JOIN ${bestTotalRelation} AS ${q("best_total")}
329
348
  WHERE ${q("__node_key")} = ${q("__end_key")}
330
- AND ${routeDistance} = ${q("best_total")}.${q("distance")}
349
+ AND ${routeMetric} = ${q("best_total")}.${q("distance")}
331
350
  ), ${rankedRelation} AS (
332
351
  SELECT CAST(row_number() OVER (ORDER BY ${q("__edge_keys")}) - 1 AS BIGINT)
333
352
  AS ${q("pathId")}, ${q("steps")}
334
353
  FROM ${shortestRelation}
335
354
  )
336
- ${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn)}`;
355
+ ${graphRouteResultSelect(rankedRelation, route.input, route.edgeIdColumn, false, metrics.elapsedTime)}`;
356
+ }
357
+ // At the same physical event a later departure (or earlier incoming arrival)
358
+ // dominates every future elapsed duration. Keyed improvements find the optimum
359
+ // without enumerating route histories; reconstruction still retains all ties.
360
+ function buildElapsedShortestPathCostSql(route, startsSelect, direction, temporal) {
361
+ const prepared = route.traversal;
362
+ const q = quoteIdentifier;
363
+ const relations = prepared.relationNames([
364
+ "graph_starts",
365
+ "graph_temporal_settings",
366
+ "graph_event_rows",
367
+ "graph_edges",
368
+ "graph_cost_states",
369
+ ]);
370
+ const startsRelation = relations.graph_starts;
371
+ const settingsRelation = relations.graph_temporal_settings;
372
+ const eventRowsRelation = relations.graph_event_rows;
373
+ const edgesRelation = relations.graph_edges;
374
+ const costRelation = relations.graph_cost_states;
375
+ const start = `${q("starts")}.${q("start")}`;
376
+ const startKey = `${q("starts")}.${q("__key")}`;
377
+ const reachedStart = `${q("reached")}.${q("start")}`;
378
+ const reachedStartKey = `${q("reached")}.${q("__start_key")}`;
379
+ const reachedNodeKey = `${q("reached")}.${q("__node_key")}`;
380
+ const edgeTo = `${q("edges")}.${q("__to")}`;
381
+ const edgeToKey = `${q("edges")}.${q("__to_key")}`;
382
+ const edgeFromKey = `${q("edges")}.${q("__from_key")}`;
383
+ const eventId = `${q("edges")}.${q("__event_id")}`;
384
+ const eventStart = `${q("edges")}.${q("__event_start")}`;
385
+ const eventEnd = `${q("edges")}.${q("__event_end")}`;
386
+ const bestDistance = `${q("best")}.${q("distance")}`;
387
+ const gap = `${q("settings")}.${q("__gap")}`;
388
+ const firstAnchor = temporal.journeyAnchor("edges", direction);
389
+ const reachedAnchor = `${q("reached")}.${q("__journey_anchor")}`;
390
+ const firstElapsed = temporal.journeyElapsed("edges", firstAnchor, direction);
391
+ const candidateDistance = temporal.journeyElapsed("edges", reachedAnchor, direction);
392
+ const bestAnchor = `${direction === "outgoing" ? "MAX" : "MIN"}(${reachedAnchor})`;
393
+ return {
394
+ costRelation,
395
+ edgesRelation,
396
+ settingsRelation,
397
+ startsRelation,
398
+ withClause: `WITH RECURSIVE ${settingsRelation} AS MATERIALIZED (
399
+ SELECT CAST(? AS HUGEINT) AS ${q("__gap")}
400
+ ), ${eventRowsRelation} AS MATERIALIZED (
401
+ ${prepared.edges(direction, [
402
+ `${route.edgeWeight} AS ${q("__weight")}`,
403
+ `${route.typedEdgeId} AS ${q("__edge_id")}`,
404
+ `${route.edgeKey} AS ${q("__edge_key")}`,
405
+ ...temporal.eventSelections("edges"),
406
+ ])}
407
+ ), ${edgesRelation} AS MATERIALIZED (
408
+ SELECT *
409
+ FROM ${eventRowsRelation} AS ${q("events")}
410
+ WHERE ${temporal.eventValidity("events")}
411
+ ), ${startsRelation} AS MATERIALIZED (
412
+ ${startsSelect}
413
+ ), ${costRelation}(
414
+ ${q("start")}, ${q("node")},
415
+ ${q("__start_key")}, ${q("__node_key")},
416
+ ${q("__event_id")}, ${q("__event_start")}, ${q("__event_end")},
417
+ ${q("distance")}, ${q("__journey_anchor")}
418
+ ) USING KEY(${q("__start_key")}, ${q("__event_id")}) AS (
419
+ SELECT ${start}, ${edgeTo}, ${startKey}, ${edgeToKey},
420
+ ${eventId}, ${eventStart}, ${eventEnd}, ${firstElapsed}, ${firstAnchor}
421
+ FROM ${startsRelation} AS ${q("starts")}
422
+ INNER JOIN ${edgesRelation} AS ${q("edges")}
423
+ ON ${startKey} = ${edgeFromKey}
424
+ UNION
425
+ SELECT ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
426
+ ${eventId}, ${eventStart}, ${eventEnd},
427
+ MIN(${candidateDistance}) AS ${q("distance")}, ${bestAnchor}
428
+ FROM ${costRelation} AS ${q("reached")}
429
+ INNER JOIN ${edgesRelation} AS ${q("edges")}
430
+ ON ${reachedNodeKey} = ${edgeFromKey}
431
+ CROSS JOIN ${settingsRelation} AS ${q("settings")}
432
+ LEFT JOIN recurring.${costRelation} AS ${q("best")}
433
+ ON ${reachedStartKey} = ${q("best")}.${q("__start_key")}
434
+ AND ${eventId} = ${q("best")}.${q("__event_id")}
435
+ WHERE ${temporal.transition("reached", "edges", direction, gap)}
436
+ GROUP BY ${reachedStart}, ${edgeTo}, ${reachedStartKey}, ${edgeToKey},
437
+ ${eventId}, ${eventStart}, ${eventEnd}, ${bestDistance}
438
+ HAVING ${bestDistance} IS NULL OR
439
+ MIN(${candidateDistance}) < ${bestDistance}
440
+ )`,
441
+ };
337
442
  }