diff --git a/src/app/view/editor-tools-view-component/editor-tools-view.component.ts b/src/app/view/editor-tools-view-component/editor-tools-view.component.ts index 15a8ce0b..2df7b7af 100644 --- a/src/app/view/editor-tools-view-component/editor-tools-view.component.ts +++ b/src/app/view/editor-tools-view-component/editor-tools-view.component.ts @@ -541,14 +541,14 @@ export class EditorToolsViewComponent { const odNodes = selectedNodes.length > 0 ? selectedNodes : this.nodeService.getVisibleNodes(); const trainruns = this.trainrunService.getVisibleTrainruns(); - const edges = buildEdges(nodes, odNodes, trainruns, connectionPenalty, this.trainrunService, timeLimit); + const [edges, tsSuccessor] = buildEdges(nodes, odNodes, trainruns, connectionPenalty, this.trainrunService, timeLimit); const neighbors = computeNeighbors(edges); const vertices = topoSort(neighbors); // In theory we could parallelize the pathfindings, but the overhead might be too big. const res = new Map(); odNodes.forEach((origin) => { - computeShortestPaths(origin.getId(), neighbors, vertices).forEach((value, key) => { + computeShortestPaths(origin.getId(), neighbors, vertices, tsSuccessor).forEach((value, key) => { res.set([origin.getId(), key].join(","), value); }); }); @@ -567,6 +567,13 @@ export class EditorToolsViewComponent { return; } const [totalCost, connections] = costs; + // Check if the reverse path has the same cost. + if (destination.getId() < origin.getId()) { + const reverseCosts = res.get([destination.getId(), origin.getId()].join(",")); + if (reverseCosts === undefined || reverseCosts[0] !== totalCost) { + console.log("Reverse path not found or different cost: ", origin.getId(), destination.getId()); + } + } const row = [origin.getBetriebspunktName(), destination.getBetriebspunktName(), (totalCost - connections * connectionPenalty).toString(), connections.toString(), totalCost.toString()]; diff --git a/src/app/view/util/origin-destination-graph.ts b/src/app/view/util/origin-destination-graph.ts index c6412a3e..ca90f970 100644 --- a/src/app/view/util/origin-destination-graph.ts +++ b/src/app/view/util/origin-destination-graph.ts @@ -14,6 +14,9 @@ export class Vertex { public time?: number, // Negative trainrun ids are used for reverse directions. public trainrunId?: number, + // In addition to the trainrunId, the trainrunSectionId allows us to check for connections + // (especially useful for trains going back to a previous node). + public trainrunSectionId?: number, ) {} } @@ -27,6 +30,8 @@ export class Edge { ) {} } +// In addition to edges, return a map of trainrunSection ids to their successor +// (in the forward direction), so we can check for connections. export const buildEdges = ( nodes: Node[], odNodes: Node[], @@ -34,16 +39,22 @@ export const buildEdges = ( connectionPenalty: number, trainrunService: TrainrunService, timeLimit: number, -): Edge[] => { - let edges = buildSectionEdges(trainruns, trainrunService, timeLimit); +): [Edge[], Map] => { + const [sectionEdges, tsSuccessor] = buildSectionEdges( + trainruns, + trainrunService, + timeLimit, + ); + let edges = sectionEdges; + // Both trainrun and trainrunSection ids are encoded in JSON keys. const verticesDepartureByTrainrunByNode = new Map< number, - Map + Map >(); const verticesArrivalByTrainrunByNode = new Map< number, - Map + Map >(); edges.forEach((edge) => { const src = edge.v1; @@ -57,29 +68,31 @@ export const buildEdges = ( const departuresByTrainrun = verticesDepartureByTrainrunByNode.get( src.nodeId, ); + const srcKey = JSON.stringify([src.trainrunId, src.trainrunSectionId]); if (departuresByTrainrun === undefined) { verticesDepartureByTrainrunByNode.set( src.nodeId, - new Map([[src.trainrunId, [src]]]), + new Map([[srcKey, [src]]]), ); } else { - const departures = departuresByTrainrun.get(src.trainrunId); + const departures = departuresByTrainrun.get(srcKey); if (departures === undefined) { - departuresByTrainrun.set(src.trainrunId, [src]); + departuresByTrainrun.set(srcKey, [src]); } else { departures.push(src); } } const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(tgt.nodeId); + const tgtKey = JSON.stringify([tgt.trainrunId, tgt.trainrunSectionId]); if (arrivalsByTrainrun === undefined) { verticesArrivalByTrainrunByNode.set( tgt.nodeId, - new Map([[tgt.trainrunId, [tgt]]]), + new Map([[tgtKey, [tgt]]]), ); } else { - const arrivals = arrivalsByTrainrun.get(tgt.trainrunId); + const arrivals = arrivalsByTrainrun.get(tgtKey); if (arrivals === undefined) { - arrivalsByTrainrun.set(tgt.trainrunId, [tgt]); + arrivalsByTrainrun.set(tgtKey, [tgt]); } else { arrivals.push(tgt); } @@ -114,10 +127,11 @@ export const buildEdges = ( verticesDepartureByTrainrunByNode, verticesArrivalByTrainrunByNode, connectionPenalty, + tsSuccessor, ), ]; - return edges; + return [edges, tsSuccessor]; }; // Given edges, return the neighbors (with weights) for each vertex, if any (outgoing adjacency list). @@ -156,7 +170,12 @@ export const computeShortestPaths = ( from: number, neighbors: Map, vertices: Vertex[], + tsSuccessor: Map, ): Map => { + const tsPredecessor = new Map(); + tsSuccessor.forEach((v, k) => { + tsPredecessor.set(v, k); + }); const res = new Map(); const dist = new Map(); let started = false; @@ -198,10 +217,17 @@ export const computeShortestPaths = ( alt < dist.get(neighborKey)[0] ) { let connection = 0; + let successor = tsSuccessor; + if (vertex.trainrunId < 0) { + successor = tsPredecessor; + } if ( vertex.trainrunId !== undefined && neighbor.trainrunId !== undefined && - vertex.trainrunId !== neighbor.trainrunId + (vertex.trainrunId !== neighbor.trainrunId || + (successor.get(vertex.trainrunSectionId) !== + neighbor.trainrunSectionId && + vertex.isDeparture === false)) ) { connection = 1; } @@ -216,38 +242,57 @@ const buildSectionEdges = ( trainruns: Trainrun[], trainrunService: TrainrunService, timeLimit: number, -): Edge[] => { +): [Edge[], Map] => { const edges = []; const its = trainrunService.getRootIterators(); + const tsSuccessor = new Map(); trainruns.forEach((trainrun) => { const tsIterator = its.get(trainrun.getId()); if (tsIterator === undefined) { console.log("Ignoring trainrun (no root found): ", trainrun.getId()); return; } - edges.push(...buildSectionEdgesFromIterator(tsIterator, false, timeLimit)); + edges.push( + ...buildSectionEdgesFromIterator( + tsIterator, + false, + timeLimit, + tsSuccessor, + ), + ); // Don't forget the reverse direction. const ts = tsIterator.current().trainrunSection; const nextIterator = trainrunService.getIterator( tsIterator.current().node, ts, ); - edges.push(...buildSectionEdgesFromIterator(nextIterator, true, timeLimit)); + edges.push( + ...buildSectionEdgesFromIterator( + nextIterator, + true, + timeLimit, + tsSuccessor, + ), + ); }); - return edges; + return [edges, tsSuccessor]; }; const buildSectionEdgesFromIterator = ( tsIterator: TrainrunIterator, reverseIterator: boolean, timeLimit: number, + tsSuccessor: Map, ): Edge[] => { const edges = []; let nonStopV1Time = -1; let nonStopV1Node = -1; + let nonStopV1TsId = -1; + let previousTsId = -1; while (tsIterator.hasNext()) { tsIterator.next(); const ts = tsIterator.current().trainrunSection; + let tsId = ts.getId(); const trainrunId = reverseIterator ? // Minus 1 so we don't conflate 0 with -0. -ts.getTrainrunId() - 1 @@ -267,20 +312,26 @@ const buildSectionEdgesFromIterator = ( if (nonStopV1Time === -1) { nonStopV1Time = v1Time; nonStopV1Node = v1Node; + nonStopV1TsId = tsId; } continue; } - let v1 = new Vertex(v1Node, true, v1Time, trainrunId); + let v1 = new Vertex(v1Node, true, v1Time, trainrunId, tsId); // If we didn't stop previously, we need to use the stored start. if (nonStopV1Time !== -1) { - v1 = new Vertex(nonStopV1Node, true, nonStopV1Time, trainrunId); + // Since we only store successors for the forward direction, + // we need to keep a consistent section id in the reverse direction as well. + if (reverseIterator) { + tsId = nonStopV1TsId; + } + v1 = new Vertex(nonStopV1Node, true, nonStopV1Time, trainrunId, tsId); nonStopV1Time = -1; } const v2Time = reverseSection ? ts.getSourceArrivalDto().consecutiveTime : ts.getTargetArrivalDto().consecutiveTime; const v2Node = reverseSection ? ts.getSourceNodeId() : ts.getTargetNodeId(); - const v2 = new Vertex(v2Node, false, v2Time, trainrunId); + const v2 = new Vertex(v2Node, false, v2Time, trainrunId, tsId); for (let i = 0; i * ts.getTrainrun().getFrequency() < timeLimit; i++) { const newV1 = new Vertex( @@ -288,24 +339,30 @@ const buildSectionEdgesFromIterator = ( v1.isDeparture, v1.time + i * ts.getTrainrun().getFrequency(), v1.trainrunId, + tsId, ); const newV2 = new Vertex( v2.nodeId, v2.isDeparture, v2.time + i * ts.getTrainrun().getFrequency(), v2.trainrunId, + tsId, ); const edge = new Edge(newV1, newV2, newV2.time - newV1.time); edges.push(edge); } + if (previousTsId !== -1 && !reverseIterator) { + tsSuccessor.set(previousTsId, tsId); + } + previousTsId = tsId; } return edges; }; const buildConvenienceEdges = ( nodes: Node[], - verticesDepartureByTrainrunByNode: Map>, - verticesArrivalByTrainrunByNode: Map>, + verticesDepartureByTrainrunByNode: Map>, + verticesArrivalByTrainrunByNode: Map>, ): Edge[] => { const edges = []; nodes.forEach((node) => { @@ -340,10 +397,15 @@ const buildConvenienceEdges = ( const buildConnectionEdges = ( nodes: Node[], - verticesDepartureByTrainrunByNode: Map>, - verticesArrivalByTrainrunByNode: Map>, + verticesDepartureByTrainrunByNode: Map>, + verticesArrivalByTrainrunByNode: Map>, connectionPenalty: number, + tsSuccessor: Map, ): Edge[] => { + const tsPredecessor = new Map(); + tsSuccessor.forEach((v, k) => { + tsPredecessor.set(v, k); + }); const edges = []; nodes.forEach((node) => { const departuresByTrainrun = verticesDepartureByTrainrunByNode.get( @@ -357,10 +419,20 @@ const buildConnectionEdges = ( arrivalsByTrainrun !== undefined ) { arrivalsByTrainrun.forEach((arrivals, arrivalTrainrunId) => { + const [arrivalTrId, arrivalTsId] = JSON.parse(arrivalTrainrunId); arrivals.forEach((arrival) => { departuresByTrainrun.forEach((departures, departureTrainrunId) => { let minDepartureTime = arrival.time; - if (arrivalTrainrunId !== departureTrainrunId) { + const [departureTrId, departureTsId] = + JSON.parse(departureTrainrunId); + let successor = tsSuccessor; + if (arrivalTrId < 0) { + successor = tsPredecessor; + } + const connection = + arrivalTrId !== departureTrId || + successor.get(arrivalTsId) !== departureTsId; + if (connection) { minDepartureTime += node.getConnectionTime(); } // For each arrival and for each trainrun available, we only want to consider the first departure. @@ -370,7 +442,7 @@ const buildConnectionEdges = ( }); if (departure !== undefined) { let cost = departure.time - arrival.time; - if (arrivalTrainrunId !== departureTrainrunId) { + if (connection) { cost += connectionPenalty; } const edge = new Edge(arrival, departure, cost); diff --git a/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts b/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts index 1018d0e4..3c5a285a 100644 --- a/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts +++ b/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts @@ -426,6 +426,246 @@ export class NetzgrafikUnitTestingOdMatrix { warnings: null, labelIds: [], }, + { + id: 1, + betriebspunktName: "I", + fullName: "I", + positionX: 832, + positionY: 32, + ports: [ + { + id: 11, + trainrunSectionId: 15, + positionIndex: 0, + positionAlignment: 1, + }, + { + id: 14, + trainrunSectionId: 16, + positionIndex: 1, + positionAlignment: 1, + }, + { + id: 10, + trainrunSectionId: 14, + positionIndex: 0, + positionAlignment: 2, + }, + { + id: 15, + trainrunSectionId: 17, + positionIndex: 0, + positionAlignment: 3, + }, + ], + transitions: [ + { + id: 4, + port1Id: 11, + port2Id: 10, + isNonStopTransit: false, + }, + { + id: 6, + port1Id: 14, + port2Id: 15, + isNonStopTransit: false, + }, + ], + connections: [], + resourceId: 2, + perronkanten: 10, + connectionTime: 5, + trainrunCategoryHaltezeiten: { + HaltezeitIPV: { + haltezeit: 3, + no_halt: false, + }, + HaltezeitA: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitB: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitC: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitD: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitUncategorized: { + haltezeit: 0, + no_halt: true, + }, + }, + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 2, + betriebspunktName: "K", + fullName: "K", + positionX: 1632, + positionY: 32, + ports: [ + { + id: 16, + trainrunSectionId: 17, + positionIndex: 0, + positionAlignment: 2, + }, + ], + transitions: [], + connections: [], + resourceId: 3, + perronkanten: 10, + connectionTime: 5, + trainrunCategoryHaltezeiten: { + HaltezeitIPV: { + haltezeit: 3, + no_halt: false, + }, + HaltezeitA: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitB: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitC: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitD: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitUncategorized: { + haltezeit: 0, + no_halt: true, + }, + }, + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 4, + betriebspunktName: "J", + fullName: "J", + positionX: 832, + positionY: 448, + ports: [ + { + id: 12, + trainrunSectionId: 15, + positionIndex: 0, + positionAlignment: 0, + }, + { + id: 13, + trainrunSectionId: 16, + positionIndex: 1, + positionAlignment: 0, + }, + ], + transitions: [ + { + id: 5, + port1Id: 12, + port2Id: 13, + isNonStopTransit: false, + }, + ], + connections: [], + resourceId: 5, + perronkanten: 5, + connectionTime: 3, + trainrunCategoryHaltezeiten: { + HaltezeitIPV: { + haltezeit: 3, + no_halt: false, + }, + HaltezeitA: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitB: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitC: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitD: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitUncategorized: { + haltezeit: 0, + no_halt: true, + }, + }, + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 7, + betriebspunktName: "H", + fullName: "H", + positionX: 320, + positionY: 32, + ports: [ + { + id: 9, + trainrunSectionId: 14, + positionIndex: 0, + positionAlignment: 3, + }, + ], + transitions: [], + connections: [], + resourceId: 8, + perronkanten: 5, + connectionTime: 5, + trainrunCategoryHaltezeiten: { + HaltezeitIPV: { + haltezeit: 3, + no_halt: false, + }, + HaltezeitA: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitB: { + haltezeit: 2, + no_halt: false, + }, + HaltezeitC: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitD: { + haltezeit: 1, + no_halt: false, + }, + HaltezeitUncategorized: { + haltezeit: 0, + no_halt: true, + }, + }, + symmetryAxis: null, + warnings: null, + labelIds: [], + }, ], trainrunSections: [ { @@ -1204,6 +1444,394 @@ export class NetzgrafikUnitTestingOdMatrix { }, warnings: null, }, + { + id: 14, + sourceNodeId: 7, + sourcePortId: 9, + targetNodeId: 1, + targetPortId: 10, + travelTime: { + time: 1, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, + }, + sourceDeparture: { + time: 0, + consecutiveTime: 0, + lock: false, + warning: null, + timeFormatter: null, + }, + sourceArrival: { + time: 0, + consecutiveTime: 180, + lock: false, + warning: null, + timeFormatter: null, + }, + targetDeparture: { + time: 59, + consecutiveTime: 179, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 1, + consecutiveTime: 1, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 2, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 418, + y: 48, + }, + { + x: 482, + y: 48, + }, + { + x: 766, + y: 48, + }, + { + x: 830, + y: 48, + }, + ], + textPositions: { + "0": { + x: 436, + y: 60, + }, + "1": { + x: 464, + y: 36, + }, + "2": { + x: 812, + y: 36, + }, + "3": { + x: 784, + y: 60, + }, + "4": { + x: 624, + y: 36, + }, + "5": { + x: 624, + y: 36, + }, + "6": { + x: 624, + y: 60, + }, + }, + }, + warnings: null, + }, + { + id: 15, + sourceNodeId: 1, + sourcePortId: 11, + targetNodeId: 4, + targetPortId: 12, + travelTime: { + time: 5, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, + }, + sourceDeparture: { + time: 3, + consecutiveTime: 3, + lock: false, + warning: null, + timeFormatter: null, + }, + sourceArrival: { + time: 57, + consecutiveTime: 177, + lock: false, + warning: null, + timeFormatter: null, + }, + targetDeparture: { + time: 52, + consecutiveTime: 172, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 8, + consecutiveTime: 8, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 2, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 848, + y: 98, + }, + { + x: 848, + y: 162, + }, + { + x: 848, + y: 382, + }, + { + x: 848, + y: 446, + }, + ], + textPositions: { + "0": { + x: 836, + y: 116, + }, + "1": { + x: 860, + y: 144, + }, + "2": { + x: 860, + y: 428, + }, + "3": { + x: 836, + y: 400, + }, + "4": { + x: 836, + y: 272, + }, + "5": { + x: 836, + y: 272, + }, + "6": { + x: 860, + y: 272, + }, + }, + }, + warnings: null, + }, + { + id: 16, + sourceNodeId: 4, + sourcePortId: 13, + targetNodeId: 1, + targetPortId: 14, + travelTime: { + time: 1, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, + }, + sourceDeparture: { + time: 6, + consecutiveTime: 66, + lock: false, + warning: null, + timeFormatter: null, + }, + sourceArrival: { + time: 54, + consecutiveTime: 114, + lock: false, + warning: null, + timeFormatter: null, + }, + targetDeparture: { + time: 53, + consecutiveTime: 113, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 7, + consecutiveTime: 67, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 2, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 880, + y: 446, + }, + { + x: 880, + y: 382, + }, + { + x: 880, + y: 162, + }, + { + x: 880, + y: 98, + }, + ], + textPositions: { + "0": { + x: 892, + y: 428, + }, + "1": { + x: 868, + y: 400, + }, + "2": { + x: 868, + y: 116, + }, + "3": { + x: 892, + y: 144, + }, + "4": { + x: 868, + y: 272, + }, + "5": { + x: 868, + y: 272, + }, + "6": { + x: 892, + y: 272, + }, + }, + }, + warnings: null, + }, + { + id: 17, + sourceNodeId: 1, + sourcePortId: 15, + targetNodeId: 2, + targetPortId: 16, + travelTime: { + time: 1, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, + }, + sourceDeparture: { + time: 9, + consecutiveTime: 69, + lock: false, + warning: null, + timeFormatter: null, + }, + sourceArrival: { + time: 51, + consecutiveTime: 111, + lock: false, + warning: null, + timeFormatter: null, + }, + targetDeparture: { + time: 50, + consecutiveTime: 110, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 10, + consecutiveTime: 70, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 2, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 930, + y: 48, + }, + { + x: 994, + y: 48, + }, + { + x: 1566, + y: 48, + }, + { + x: 1630, + y: 48, + }, + ], + textPositions: { + "0": { + x: 948, + y: 60, + }, + "1": { + x: 976, + y: 36, + }, + "2": { + x: 1612, + y: 36, + }, + "3": { + x: 1584, + y: 60, + }, + "4": { + x: 1280, + y: 36, + }, + "5": { + x: 1280, + y: 36, + }, + "6": { + x: 1280, + y: 60, + }, + }, + }, + warnings: null, + }, ], trainruns: [ { @@ -1246,6 +1874,14 @@ export class NetzgrafikUnitTestingOdMatrix { trainrunTimeCategoryId: 0, labelIds: [], }, + { + id: 2, + name: "X", + categoryId: 1, + frequencyId: 3, + trainrunTimeCategoryId: 0, + labelIds: [], + }, ], resources: [ { diff --git a/src/integration-testing/origin.destination.csv.test.spec.ts b/src/integration-testing/origin.destination.csv.test.spec.ts index f3845ba0..a3b26841 100644 --- a/src/integration-testing/origin.destination.csv.test.spec.ts +++ b/src/integration-testing/origin.destination.csv.test.spec.ts @@ -88,7 +88,7 @@ describe("Origin Destination CSV Test", () => { const timeLimit = 60 * 10; const start = new Date().getTime(); - const edges = buildEdges( + const [edges, tsSuccessor] = buildEdges( nodes, nodes, trainruns, @@ -102,11 +102,14 @@ describe("Origin Destination CSV Test", () => { const res = new Map(); nodes.forEach((origin) => { - computeShortestPaths(origin.getId(), neighbors, vertices).forEach( - (value, key) => { - res.set([origin.getId(), key].join(","), value); - }, - ); + computeShortestPaths( + origin.getId(), + neighbors, + vertices, + tsSuccessor, + ).forEach((value, key) => { + res.set([origin.getId(), key].join(","), value); + }); }); const end = new Date().getTime(); @@ -132,6 +135,20 @@ describe("Origin Destination CSV Test", () => { ["16,17", [1, 0]], ["17,15", [4, 0]], ["17,16", [1, 0]], + // Making sure we consider connections in H -> I -> J -> I -> K. + ["1,7", [1, 0]], + ["1,2", [1, 0]], + ["1,4", [1, 0]], + ["2,7", [15, 1]], + ["2,4", [4, 0]], + ["2,1", [1, 0]], + ["4,7", [8, 0]], + ["4,2", [4, 0]], + ["4,1", [1, 0]], + ["7,2", [15, 1]], + ["7,4", [8, 0]], + ["7,1", [1, 0]], + // TODO: ideally we would test L -> M -> Non-stop -> O works well in both directions. ]), ); // This should be reasonably fast, likely less than 10ms. @@ -150,7 +167,7 @@ describe("Origin Destination CSV Test", () => { const connectionPenalty = 5; const timeLimit = 60 * 10; - const edges = buildEdges( + const [edges, tsSuccessor] = buildEdges( nodes, odNodes, trainruns, @@ -164,11 +181,14 @@ describe("Origin Destination CSV Test", () => { const res = new Map(); nodes.forEach((origin) => { - computeShortestPaths(origin.getId(), neighbors, vertices).forEach( - (value, key) => { - res.set([origin.getId(), key].join(","), value); - }, - ); + computeShortestPaths( + origin.getId(), + neighbors, + vertices, + tsSuccessor, + ).forEach((value, key) => { + res.set([origin.getId(), key].join(","), value); + }); }); // See https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/issues/199 @@ -182,13 +202,14 @@ describe("Origin Destination CSV Test", () => { it("simple path unit test", () => { const v1 = new Vertex(0, true); - const v2 = new Vertex(0, true, 0, 0); - const v3 = new Vertex(1, false, 15, 0); + const v2 = new Vertex(0, true, 0, 0, 0); + const v3 = new Vertex(1, false, 15, 0, 0); const v4 = new Vertex(1, false); const e1 = new Edge(v1, v2, 0); const e2 = new Edge(v2, v3, 15); const e3 = new Edge(v3, v4, 0); const edges = [e1, e2, e3]; + const tsSuccessor = new Map(); const neighbors = computeNeighbors(edges); @@ -214,7 +235,12 @@ describe("Origin Destination CSV Test", () => { expect(v1Index).toBeLessThan(v2Index); }); - const distances0 = computeShortestPaths(0, neighbors, topoVertices); + const distances0 = computeShortestPaths( + 0, + neighbors, + topoVertices, + tsSuccessor, + ); expect(distances0).toHaveSize(1); expect(distances0.get(1)).toEqual([15, 0]); @@ -223,10 +249,10 @@ describe("Origin Destination CSV Test", () => { it("connection unit test", () => { // trainrun 0 const v1 = new Vertex(0, true); - const v2 = new Vertex(0, true, 0, 0); - const v3 = new Vertex(1, false, 15, 0); - const v4 = new Vertex(1, true, 16, 0); - const v5 = new Vertex(2, false, 30, 0); + const v2 = new Vertex(0, true, 0, 0, 0); + const v3 = new Vertex(1, false, 15, 0, 0); + const v4 = new Vertex(1, true, 16, 0, 1); + const v5 = new Vertex(2, false, 30, 0, 1); const v6 = new Vertex(2, false); const e1 = new Edge(v1, v2, 0); const e2 = new Edge(v2, v3, 15); @@ -235,8 +261,8 @@ describe("Origin Destination CSV Test", () => { const e5 = new Edge(v5, v6, 0); // trainrun 1 const v7 = new Vertex(3, true); - const v8 = new Vertex(3, true, 0, 1); - const v9 = new Vertex(1, false, 10, 1); + const v8 = new Vertex(3, true, 0, 1, 2); + const v9 = new Vertex(1, false, 10, 1, 2); const e6 = new Edge(v7, v8, 0); const e7 = new Edge(v8, v9, 10); // connection @@ -248,6 +274,7 @@ describe("Origin Destination CSV Test", () => { const v11 = new Vertex(1, true); const e11 = new Edge(v11, v4, 0); const edges = [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11]; + const tsSuccessor = new Map([[0, 1]]); const neighbors = computeNeighbors(edges); expect(neighbors).toHaveSize(9); @@ -273,16 +300,31 @@ describe("Origin Destination CSV Test", () => { expect(v1Index).toBeLessThan(v2Index); }); - const distances0 = computeShortestPaths(0, neighbors, topoVertices); + const distances0 = computeShortestPaths( + 0, + neighbors, + topoVertices, + tsSuccessor, + ); expect(distances0).toHaveSize(2); expect(distances0.get(1)).toEqual([15, 0]); expect(distances0.get(2)).toEqual([30, 0]); - const distances1 = computeShortestPaths(1, neighbors, topoVertices); + const distances1 = computeShortestPaths( + 1, + neighbors, + topoVertices, + tsSuccessor, + ); expect(distances1).toHaveSize(1); expect(distances1.get(2)).toEqual([14, 0]); - const distances3 = computeShortestPaths(3, neighbors, topoVertices); + const distances3 = computeShortestPaths( + 3, + neighbors, + topoVertices, + tsSuccessor, + ); expect(distances3).toHaveSize(2); expect(distances3.get(1)).toEqual([10, 0]); // connection