@nshiab/simple-data-analysis-core 2.1.6 → 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.
- package/esm/class/SimpleTable.d.ts +249 -23
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +243 -23
- package/esm/helpers/graphRouteResultSchema.d.ts +1 -1
- package/esm/helpers/graphRouteResultSchema.d.ts.map +1 -1
- package/esm/helpers/graphRouteResultSchema.js +3 -2
- package/esm/helpers/graphRouteResultSelect.d.ts +1 -1
- package/esm/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/esm/helpers/graphRouteResultSelect.js +4 -1
- package/esm/helpers/prepareGraphMetricOptions.d.ts +14 -0
- package/esm/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
- package/esm/helpers/prepareGraphMetricOptions.js +40 -0
- package/esm/helpers/prepareGraphTemporalSql.d.ts +3 -0
- package/esm/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
- package/esm/helpers/prepareGraphTemporalSql.js +7 -0
- package/esm/methods/distances.d.ts +2 -0
- package/esm/methods/distances.d.ts.map +1 -1
- package/esm/methods/distances.js +98 -6
- package/esm/methods/findCycles.d.ts +1 -0
- package/esm/methods/findCycles.d.ts.map +1 -1
- package/esm/methods/findCycles.js +22 -15
- package/esm/methods/paths.d.ts +1 -0
- package/esm/methods/paths.d.ts.map +1 -1
- package/esm/methods/paths.js +21 -14
- package/esm/methods/shortestPath.d.ts +2 -0
- package/esm/methods/shortestPath.d.ts.map +1 -1
- package/esm/methods/shortestPath.js +125 -20
- package/llm.md +278 -53
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +249 -23
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +243 -23
- package/script/helpers/graphRouteResultSchema.d.ts +1 -1
- package/script/helpers/graphRouteResultSchema.d.ts.map +1 -1
- package/script/helpers/graphRouteResultSchema.js +3 -2
- package/script/helpers/graphRouteResultSelect.d.ts +1 -1
- package/script/helpers/graphRouteResultSelect.d.ts.map +1 -1
- package/script/helpers/graphRouteResultSelect.js +4 -1
- package/script/helpers/prepareGraphMetricOptions.d.ts +14 -0
- package/script/helpers/prepareGraphMetricOptions.d.ts.map +1 -0
- package/script/helpers/prepareGraphMetricOptions.js +43 -0
- package/script/helpers/prepareGraphTemporalSql.d.ts +3 -0
- package/script/helpers/prepareGraphTemporalSql.d.ts.map +1 -1
- package/script/helpers/prepareGraphTemporalSql.js +7 -0
- package/script/methods/distances.d.ts +2 -0
- package/script/methods/distances.d.ts.map +1 -1
- package/script/methods/distances.js +98 -6
- package/script/methods/findCycles.d.ts +1 -0
- package/script/methods/findCycles.d.ts.map +1 -1
- package/script/methods/findCycles.js +22 -15
- package/script/methods/paths.d.ts +1 -0
- package/script/methods/paths.d.ts.map +1 -1
- package/script/methods/paths.js +21 -14
- package/script/methods/shortestPath.d.ts +2 -0
- package/script/methods/shortestPath.d.ts.map +1 -1
- 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 =
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
}
|