- Add transfer depth limiting in BFS/Dijkstra (MaxTransfers field in SearchOptions) - Track transfer count at each step; stop when depth > 5 - Synthetic edge fallback on expansion failure - Add TestLazyExpansionDepthLimit and TestFindRouteMaxTransfers tests Co-Authored-By: Claude <noreply@anthropic.com>
152 lines
4.9 KiB
Go
152 lines
4.9 KiB
Go
package routing
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
func TestFindRouteMaxTransfers(t *testing.T) {
|
|
graph := NewGraph()
|
|
|
|
// Create 6 stations: s1, s2, s3, s4, s5, s6
|
|
for i := 0; i < 6; i++ {
|
|
graph.AddNode(&Node{ID: fmt.Sprintf("s%d", i+1), Type: NodeTypeStation, Name: fmt.Sprintf("Station %d", i+1), CityCode: "c1"})
|
|
}
|
|
|
|
// Add direct edge s1 -> s6 (0 transfers)
|
|
graph.AddEdge(&Edge{
|
|
From: graph.Nodes()[0], // s1
|
|
To: graph.Nodes()[5], // s6
|
|
Kind: EdgeKindReal,
|
|
Duration: 3600,
|
|
Transport: "train",
|
|
TransportType: TransportTypeTrain,
|
|
IsTransfer: false,
|
|
})
|
|
|
|
// Add chain edges s1->s2->s3->s4->s5->s6 (each is a transfer edge)
|
|
for i := 0; i < 5; i++ {
|
|
graph.AddEdge(&Edge{
|
|
From: graph.Nodes()[i],
|
|
To: graph.Nodes()[i+1],
|
|
Kind: EdgeKindReal,
|
|
Duration: 1000,
|
|
Transport: "train",
|
|
TransportType: TransportTypeTrain,
|
|
IsTransfer: true,
|
|
})
|
|
}
|
|
|
|
// Test with MaxTransfers=0: should only find the direct route (0 transfers)
|
|
opts0 := SearchOptions{MaxTransfers: 0}
|
|
results0 := graph.FindRoutesPareto("s1", "s6", opts0)
|
|
t.Logf("MaxTransfers=0: found %d route(s)", len(results0))
|
|
for _, r := range results0 {
|
|
t.Logf(" Route: duration=%d, transfers=%d", r.TotalDuration, r.TotalTransfers)
|
|
}
|
|
// Should find the direct route (0 transfers)
|
|
directFound := false
|
|
for _, r := range results0 {
|
|
if r.TotalTransfers == 0 {
|
|
directFound = true
|
|
break
|
|
}
|
|
}
|
|
if !directFound {
|
|
t.Error("expected direct route (0 transfers) with MaxTransfers=0")
|
|
return
|
|
}
|
|
|
|
// Test with MaxTransfers=1: should find direct route + 1-transfer route if any
|
|
opts1 := SearchOptions{MaxTransfers: 1}
|
|
results1 := graph.FindRoutesPareto("s1", "s6", opts1)
|
|
t.Logf("MaxTransfers=1: found %d route(s)", len(results1))
|
|
for _, r := range results1 {
|
|
t.Logf(" Route: duration=%d, transfers=%d", r.TotalDuration, r.TotalTransfers)
|
|
}
|
|
// Verify no route has more than 1 transfer
|
|
for _, r := range results1 {
|
|
if r.TotalTransfers > 1 {
|
|
t.Errorf("route with MaxTransfers=1 has %d transfers, expected <= 1", r.TotalTransfers)
|
|
}
|
|
}
|
|
|
|
// Test with MaxTransfers=2: should find more routes
|
|
opts2 := SearchOptions{MaxTransfers: 2}
|
|
results2 := graph.FindRoutesPareto("s1", "s6", opts2)
|
|
t.Logf("MaxTransfers=2: found %d route(s)", len(results2))
|
|
for _, r := range results2 {
|
|
t.Logf(" Route: duration=%d, transfers=%d", r.TotalDuration, r.TotalTransfers)
|
|
}
|
|
// Verify no route has more than 2 transfers
|
|
for _, r := range results2 {
|
|
if r.TotalTransfers > 2 {
|
|
t.Errorf("route with MaxTransfers=2 has %d transfers, expected <= 2", r.TotalTransfers)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLazyExpansionDepthLimit tests that BFS stops expanding when transfer depth exceeds MaxTransfers.
|
|
func TestLazyExpansionDepthLimit(t *testing.T) {
|
|
graph := NewGraph()
|
|
|
|
// Create 7 stations: s1, s2, s3, s4, s5, s6, s7
|
|
for i := 0; i < 7; i++ {
|
|
graph.AddNode(&Node{ID: fmt.Sprintf("s%d", i+1), Type: NodeTypeStation, Name: fmt.Sprintf("Station %d", i+1), CityCode: "c1"})
|
|
}
|
|
|
|
// Add chain of transfer edges s1->s2->s3->s4->s5->s6->s7
|
|
for i := 0; i < 6; i++ {
|
|
graph.AddEdge(&Edge{
|
|
From: graph.Nodes()[i],
|
|
To: graph.Nodes()[i+1],
|
|
Kind: EdgeKindReal,
|
|
Duration: 100,
|
|
Transport: "train",
|
|
TransportType: TransportTypeTrain,
|
|
IsTransfer: true,
|
|
})
|
|
}
|
|
|
|
// Test with MaxTransfers=2: should only find routes with <= 2 transfers
|
|
opts2 := SearchOptions{MaxTransfers: 2}
|
|
results2 := graph.FindRoute("s1", "s7", opts2)
|
|
if results2 != nil {
|
|
t.Logf("MaxTransfers=2: found route with %d transfers", results2.TotalTransfers)
|
|
for _, leg := range results2.Legs {
|
|
t.Logf(" Leg: %s -> %s (isTransfer=%v)", leg.From.Name, leg.To.Name, leg.IsTransfer)
|
|
}
|
|
// With MaxTransfers=2, a chain of 6 transfers (s1->...->s7) should not be found
|
|
if results2.TotalTransfers > 2 {
|
|
t.Errorf("expected <= 2 transfers with MaxTransfers=2, got %d", results2.TotalTransfers)
|
|
}
|
|
}
|
|
|
|
// Test with MaxTransfers=5: should allow routes with up to 5 transfers
|
|
opts5 := SearchOptions{MaxTransfers: 5}
|
|
results5 := graph.FindRoute("s1", "s7", opts5)
|
|
if results5 != nil {
|
|
t.Logf("MaxTransfers=5: found route with %d transfers", results5.TotalTransfers)
|
|
if results5.TotalTransfers > 5 {
|
|
t.Errorf("expected <= 5 transfers with MaxTransfers=5, got %d", results5.TotalTransfers)
|
|
}
|
|
} else {
|
|
t.Log("MaxTransfers=5: no route found (linear chain may still exceed limit)")
|
|
}
|
|
|
|
// Test with MaxTransfers=0: should only find direct routes (no transfers)
|
|
opts0 := SearchOptions{MaxTransfers: 0}
|
|
results0 := graph.FindRoute("s1", "s7", opts0)
|
|
if results0 != nil {
|
|
t.Logf("MaxTransfers=0: found route with %d transfers", results0.TotalTransfers)
|
|
for _, leg := range results0.Legs {
|
|
t.Logf(" Leg: %s -> %s (isTransfer=%v)", leg.From.Name, leg.To.Name, leg.IsTransfer)
|
|
}
|
|
if results0.TotalTransfers != 0 {
|
|
t.Errorf("expected 0 transfers with MaxTransfers=0, got %d", results0.TotalTransfers)
|
|
}
|
|
} else {
|
|
t.Log("MaxTransfers=0: no direct route s1->s7 found (only chain edges exist)")
|
|
}
|
|
}
|