Files
Sportstime/SportsTimeTests/Helpers/BruteForceRouteVerifier.swift
Trey t 1bd248c255 test(planning): complete test suite with Phase 11 edge cases
Implement comprehensive test infrastructure and all 124 tests across 11 phases:

- Phase 0: Test infrastructure (fixtures, mocks, helpers)
- Phases 1-10: Core planning engine tests (previously implemented)
- Phase 11: Edge case omnibus (11 new tests)
  - Data edge cases: nil stadiums, malformed dates, invalid coordinates
  - Boundary conditions: driving limits, radius boundaries
  - Time zone cases: cross-timezone games, DST transitions

Reorganize test structure under Planning/ directory with proper organization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:14:40 -06:00

306 lines
9.9 KiB
Swift

//
// BruteForceRouteVerifier.swift
// SportsTimeTests
//
// Exhaustively enumerates all route permutations to verify optimality.
// Used for inputs with 8 stops where brute force is feasible.
//
import Foundation
import CoreLocation
@testable import SportsTime
// MARK: - Route Verifier
struct BruteForceRouteVerifier {
// MARK: - Route Comparison Result
struct VerificationResult {
let isOptimal: Bool
let proposedRouteDistance: Double
let optimalRouteDistance: Double
let optimalRoute: [UUID]?
let improvement: Double? // Percentage improvement if not optimal
let permutationsChecked: Int
var improvementPercentage: Double? {
guard let improvement = improvement else { return nil }
return improvement * 100
}
}
// MARK: - Verification
/// Verify that a proposed route is optimal (or near-optimal) by checking all permutations
/// - Parameters:
/// - proposedRoute: The route to verify (ordered list of stop IDs)
/// - stops: Dictionary mapping stop IDs to their coordinates
/// - tolerance: Percentage tolerance for "near-optimal" (default 0 = must be exactly optimal)
/// - Returns: Verification result
static func verify(
proposedRoute: [UUID],
stops: [UUID: CLLocationCoordinate2D],
tolerance: Double = 0
) -> VerificationResult {
guard proposedRoute.count <= TestConstants.bruteForceMaxStops else {
fatalError("BruteForceRouteVerifier should only be used for ≤\(TestConstants.bruteForceMaxStops) stops")
}
guard proposedRoute.count >= 2 else {
// Single stop or empty - trivially optimal
return VerificationResult(
isOptimal: true,
proposedRouteDistance: 0,
optimalRouteDistance: 0,
optimalRoute: proposedRoute,
improvement: nil,
permutationsChecked: 1
)
}
let proposedDistance = calculateRouteDistance(proposedRoute, stops: stops)
// Find optimal route by checking all permutations
let allPermutations = permutations(of: proposedRoute)
var optimalDistance = Double.infinity
var optimalRoute: [UUID] = []
for permutation in allPermutations {
let distance = calculateRouteDistance(permutation, stops: stops)
if distance < optimalDistance {
optimalDistance = distance
optimalRoute = permutation
}
}
let isOptimal: Bool
var improvement: Double? = nil
if tolerance == 0 {
// Exact optimality check with floating point tolerance
isOptimal = abs(proposedDistance - optimalDistance) < 0.001
} else {
// Within tolerance
let maxAllowed = optimalDistance * (1 + tolerance)
isOptimal = proposedDistance <= maxAllowed
}
if !isOptimal && optimalDistance > 0 {
improvement = (proposedDistance - optimalDistance) / optimalDistance
}
return VerificationResult(
isOptimal: isOptimal,
proposedRouteDistance: proposedDistance,
optimalRouteDistance: optimalDistance,
optimalRoute: optimalRoute,
improvement: improvement,
permutationsChecked: allPermutations.count
)
}
/// Verify a route is optimal with a fixed start and end point
static func verifyWithFixedEndpoints(
proposedRoute: [UUID],
stops: [UUID: CLLocationCoordinate2D],
startId: UUID,
endId: UUID,
tolerance: Double = 0
) -> VerificationResult {
guard proposedRoute.first == startId && proposedRoute.last == endId else {
// Invalid route - doesn't match required endpoints
return VerificationResult(
isOptimal: false,
proposedRouteDistance: Double.infinity,
optimalRouteDistance: 0,
optimalRoute: nil,
improvement: nil,
permutationsChecked: 0
)
}
// Get intermediate stops (excluding start and end)
let intermediateStops = proposedRoute.dropFirst().dropLast()
guard intermediateStops.count <= TestConstants.bruteForceMaxStops - 2 else {
fatalError("BruteForceRouteVerifier: too many intermediate stops")
}
let proposedDistance = calculateRouteDistance(proposedRoute, stops: stops)
// Generate all permutations of intermediate stops
let allPermutations = permutations(of: Array(intermediateStops))
var optimalDistance = Double.infinity
var optimalRoute: [UUID] = []
for permutation in allPermutations {
var fullRoute = [startId]
fullRoute.append(contentsOf: permutation)
fullRoute.append(endId)
let distance = calculateRouteDistance(fullRoute, stops: stops)
if distance < optimalDistance {
optimalDistance = distance
optimalRoute = fullRoute
}
}
let isOptimal: Bool
var improvement: Double? = nil
if tolerance == 0 {
isOptimal = abs(proposedDistance - optimalDistance) < 0.001
} else {
let maxAllowed = optimalDistance * (1 + tolerance)
isOptimal = proposedDistance <= maxAllowed
}
if !isOptimal && optimalDistance > 0 {
improvement = (proposedDistance - optimalDistance) / optimalDistance
}
return VerificationResult(
isOptimal: isOptimal,
proposedRouteDistance: proposedDistance,
optimalRouteDistance: optimalDistance,
optimalRoute: optimalRoute,
improvement: improvement,
permutationsChecked: allPermutations.count
)
}
/// Check if there's an obviously better route (significantly shorter)
static func hasObviouslyBetterRoute(
proposedRoute: [UUID],
stops: [UUID: CLLocationCoordinate2D],
threshold: Double = 0.1 // 10% improvement threshold
) -> (hasBetter: Bool, improvement: Double?) {
let result = verify(proposedRoute: proposedRoute, stops: stops, tolerance: threshold)
return (!result.isOptimal, result.improvement)
}
// MARK: - Distance Calculation
/// Calculate total route distance using haversine formula
static func calculateRouteDistance(
_ route: [UUID],
stops: [UUID: CLLocationCoordinate2D]
) -> Double {
guard route.count >= 2 else { return 0 }
var totalDistance: Double = 0
for i in 0..<(route.count - 1) {
guard let from = stops[route[i]],
let to = stops[route[i + 1]] else {
continue
}
totalDistance += haversineDistanceMiles(from: from, to: to)
}
return totalDistance
}
/// Haversine distance between two coordinates in miles
static func haversineDistanceMiles(
from: CLLocationCoordinate2D,
to: CLLocationCoordinate2D
) -> Double {
let earthRadiusMiles = TestConstants.earthRadiusMiles
let lat1 = from.latitude * .pi / 180
let lat2 = to.latitude * .pi / 180
let deltaLat = (to.latitude - from.latitude) * .pi / 180
let deltaLon = (to.longitude - from.longitude) * .pi / 180
let a = sin(deltaLat / 2) * sin(deltaLat / 2) +
cos(lat1) * cos(lat2) *
sin(deltaLon / 2) * sin(deltaLon / 2)
let c = 2 * atan2(sqrt(a), sqrt(1 - a))
return earthRadiusMiles * c
}
// MARK: - Permutation Generation
/// Generate all permutations of an array (Heap's algorithm)
static func permutations<T>(of array: [T]) -> [[T]] {
var result: [[T]] = []
var arr = array
func generate(_ n: Int) {
if n == 1 {
result.append(arr)
return
}
for i in 0..<n {
generate(n - 1)
if n % 2 == 0 {
arr.swapAt(i, n - 1)
} else {
arr.swapAt(0, n - 1)
}
}
}
generate(array.count)
return result
}
// MARK: - Factorial
/// Calculate factorial (for estimating permutation count)
static func factorial(_ n: Int) -> Int {
guard n > 1 else { return 1 }
return (1...n).reduce(1, *)
}
}
// MARK: - Convenience Extensions
extension BruteForceRouteVerifier {
/// Verify a trip's route is optimal
static func verifyTrip(_ trip: Trip) -> VerificationResult {
var stops: [UUID: CLLocationCoordinate2D] = [:]
for stop in trip.stops {
if let coord = stop.coordinate {
stops[stop.id] = coord
}
}
let routeIds = trip.stops.map { $0.id }
return verify(proposedRoute: routeIds, stops: stops)
}
/// Verify a list of stadiums forms an optimal route
static func verifyStadiumRoute(_ stadiums: [Stadium]) -> VerificationResult {
let stops = Dictionary(uniqueKeysWithValues: stadiums.map { ($0.id, $0.coordinate) })
let routeIds = stadiums.map { $0.id }
return verify(proposedRoute: routeIds, stops: stops)
}
}
// MARK: - Test Assertions
extension BruteForceRouteVerifier.VerificationResult {
/// Returns a detailed failure message if not optimal
var failureMessage: String? {
guard !isOptimal else { return nil }
var message = "Route is not optimal. "
message += "Proposed: \(String(format: "%.1f", proposedRouteDistance)) miles, "
message += "Optimal: \(String(format: "%.1f", optimalRouteDistance)) miles"
if let improvement = improvementPercentage {
message += " (\(String(format: "%.1f", improvement))% longer)"
}
message += ". Checked \(permutationsChecked) permutations."
return message
}
}