diff --git a/main/pom.xml b/main/pom.xml index c8bafc5..20d6ddc 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -11,6 +11,17 @@ 17 17 + UTF-8 + + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + \ No newline at end of file diff --git a/main/src/main/java/sd/Entry.java b/main/src/main/java/sd/Entry.java index b1d76a6..e8ed01e 100644 --- a/main/src/main/java/sd/Entry.java +++ b/main/src/main/java/sd/Entry.java @@ -1,7 +1,81 @@ package sd; +import java.io.IOException; + +import sd.config.SimulationConfig; +import sd.engine.SimulationEngine; + +/** + * Main entry point for the traffic simulation. + * + * This class initializes and runs the discrete event simulation. + */ public class Entry { + + private static final String DEFAULT_CONFIG_FILE = "src/main/resources/simulation.properties"; + public static void main(String[] args) { - System.out.println("Hello, World!"); + System.out.println("=".repeat(60)); + System.out.println("TRAFFIC SIMULATION - DISCRETE EVENT SIMULATOR"); + System.out.println("=".repeat(60)); + + try { + // Load configuration + String configFile = args.length > 0 ? args[0] : DEFAULT_CONFIG_FILE; + System.out.println("Loading configuration from: " + configFile); + + SimulationConfig config = new SimulationConfig(configFile); + + // Display configuration + displayConfiguration(config); + + // Create and initialize simulation engine + SimulationEngine engine = new SimulationEngine(config); + engine.initialize(); + + System.out.println("\n" + "=".repeat(60)); + + // Run simulation + long startTime = System.currentTimeMillis(); + engine.run(); + long endTime = System.currentTimeMillis(); + + // Display execution time + double executionTime = (endTime - startTime) / 1000.0; + System.out.println("\nExecution time: " + String.format("%.2f", executionTime) + " seconds"); + System.out.println("=".repeat(60)); + + } catch (IOException e) { + System.err.println("Error loading configuration: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("Error during simulation: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Displays the main configuration parameters. + */ + private static void displayConfiguration(SimulationConfig config) { + System.out.println("\nSIMULATION CONFIGURATION:"); + System.out.println(" Duration: " + config.getSimulationDuration() + " seconds"); + System.out.println(" Arrival Model: " + config.getArrivalModel()); + + if ("POISSON".equalsIgnoreCase(config.getArrivalModel())) { + System.out.println(" Arrival Rate (λ): " + config.getArrivalRate() + " vehicles/second"); + } else { + System.out.println(" Fixed Interval: " + config.getFixedArrivalInterval() + " seconds"); + } + + System.out.println(" Statistics Update Interval: " + config.getStatisticsUpdateInterval() + " seconds"); + + System.out.println("\nVEHICLE TYPES:"); + System.out.println(" Bike: " + (config.getBikeVehicleProbability() * 100) + "% " + + "(crossing time: " + config.getBikeVehicleCrossingTime() + "s)"); + System.out.println(" Light: " + (config.getLightVehicleProbability() * 100) + "% " + + "(crossing time: " + config.getLightVehicleCrossingTime() + "s)"); + System.out.println(" Heavy: " + (config.getHeavyVehicleProbability() * 100) + "% " + + "(crossing time: " + config.getHeavyVehicleCrossingTime() + "s)"); } } diff --git a/main/src/main/java/sd/engine/SimulationEngine.java b/main/src/main/java/sd/engine/SimulationEngine.java new file mode 100644 index 0000000..eac4fcc --- /dev/null +++ b/main/src/main/java/sd/engine/SimulationEngine.java @@ -0,0 +1,487 @@ +package sd.engine; + +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +import sd.config.SimulationConfig; +import sd.model.Event; +import sd.model.EventType; +import sd.model.Intersection; +import sd.model.TrafficLight; +import sd.model.TrafficLightState; +import sd.model.Vehicle; +import sd.model.VehicleType; +import sd.util.StatisticsCollector; +import sd.util.VehicleGenerator; + +/** + * Core simulation engine using discrete event simulation with a priority queue. + * + * Processes events in chronological order, managing traffic lights, vehicles, + * and routing throughout the network of intersections. + */ +public class SimulationEngine { + + private final SimulationConfig config; + private final PriorityQueue eventQueue; + private final Map intersections; + private final VehicleGenerator vehicleGenerator; + private final StatisticsCollector statisticsCollector; + + private double currentTime; + private int vehicleCounter; + + public SimulationEngine(SimulationConfig config) { + this.config = config; + this.eventQueue = new PriorityQueue<>(); + this.intersections = new HashMap<>(); + this.vehicleGenerator = new VehicleGenerator(config); + this.statisticsCollector = new StatisticsCollector(config); + this.currentTime = 0.0; + this.vehicleCounter = 0; + } + + /** + * Initializes the simulation by creating intersections, traffic lights, + * and scheduling initial events. + */ + public void initialize() { + System.out.println("Initializing simulation..."); + + // Create intersections + setupIntersections(); + + // Configure routing between intersections + setupRouting(); + + // Schedule initial traffic light changes + scheduleTrafficLightEvents(); + + // Schedule first vehicle generation + scheduleNextVehicleGeneration(0.0); + + // Schedule periodic statistics updates + scheduleStatisticsUpdates(); + + System.out.println("Simulation initialized with " + intersections.size() + " intersections"); + } + + /** + * Creates all intersections with their traffic lights. + */ + private void setupIntersections() { + String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"}; + String[] directions = {/*"North",*/ "South", "East", "West"}; + + for (String id : intersectionIds) { + Intersection intersection = new Intersection(id); + + // Add traffic lights for each direction + for (String direction : directions) { + double greenTime = config.getTrafficLightGreenTime(id, direction); + double redTime = config.getTrafficLightRedTime(id, direction); + + TrafficLight light = new TrafficLight( + id + "-" + direction, + direction, + greenTime, + redTime + ); + + intersection.addTrafficLight(light); + } + + intersections.put(id, intersection); + } + } + + /** + * Configures how vehicles should be routed between intersections. + * This is a simplified routing - in a real scenario, this would be more complex. + */ + private void setupRouting() { + // Example routing configuration (simplified) + // Each intersection routes to next destinations based on direction + + // Cr1 routing + intersections.get("Cr1").configureRoute("Cr2", "East"); + intersections.get("Cr1").configureRoute("Cr4", "South"); + + // Cr2 routing + intersections.get("Cr2").configureRoute("Cr1", "West"); + intersections.get("Cr2").configureRoute("Cr3", "East"); + intersections.get("Cr2").configureRoute("Cr5", "South"); + + // Cr3 routing + intersections.get("Cr3").configureRoute("Cr2", "West"); + intersections.get("Cr3").configureRoute("S", "South"); + + // Cr4 routing + //intersections.get("Cr4").configureRoute("Cr1", "North"); + intersections.get("Cr4").configureRoute("Cr5", "East"); + + // Cr5 routing + //intersections.get("Cr5").configureRoute("Cr2", "North"); + //intersections.get("Cr5").configureRoute("Cr4", "West"); + intersections.get("Cr5").configureRoute("S", "East"); + } + + /** + * Schedules initial traffic light change events for all intersections. + */ + private void scheduleTrafficLightEvents() { + for (Intersection intersection : intersections.values()) { + for (TrafficLight light : intersection.getTrafficLights()) { + // Start with lights in RED state, schedule first GREEN change + // Stagger the start times slightly to avoid all lights changing at once + double staggerDelay = Math.random() * 5.0; + scheduleTrafficLightChange(light, intersection.getId(), staggerDelay); + } + } + } + + /** + * Schedules the next traffic light state change. + */ + private void scheduleTrafficLightChange(TrafficLight light, String intersectionId, double delay) { + double changeTime = currentTime + delay; + Event event = new Event(changeTime, EventType.TRAFFIC_LIGHT_CHANGE, light, intersectionId); + eventQueue.offer(event); + } + + /** + * Schedules the next vehicle generation event. + */ + private void scheduleNextVehicleGeneration(double baseTime) { + double nextArrivalTime = vehicleGenerator.getNextArrivalTime(baseTime); + + if (nextArrivalTime < config.getSimulationDuration()) { + Event event = new Event(nextArrivalTime, EventType.VEHICLE_GENERATION, null, null); + eventQueue.offer(event); + } + } + + /** + * Schedules periodic statistics update events. + */ + private void scheduleStatisticsUpdates() { + double interval = config.getStatisticsUpdateInterval(); + double duration = config.getSimulationDuration(); + + for (double time = interval; time < duration; time += interval) { + Event event = new Event(time, EventType.STATISTICS_UPDATE, null, null); + eventQueue.offer(event); + } + } + + /** + * Runs the simulation until the time limit or until no more events. + */ + public void run() { + System.out.println("Starting simulation..."); + double duration = config.getSimulationDuration(); + + while (!eventQueue.isEmpty() && currentTime < duration) { + Event event = eventQueue.poll(); + currentTime = event.getTimestamp(); + + // Process the event based on its type + processEvent(event); + } + + System.out.println("\nSimulation completed at t=" + String.format("%.2f", currentTime) + "s"); + printFinalStatistics(); + } + + /** + * Processes a single event based on its type. + */ + private void processEvent(Event event) { + switch (event.getType()) { + case VEHICLE_GENERATION: + handleVehicleGeneration(); + break; + + case VEHICLE_ARRIVAL: + handleVehicleArrival(event); + break; + + case TRAFFIC_LIGHT_CHANGE: + handleTrafficLightChange(event); + break; + + case CROSSING_START: + handleCrossingStart(event); + break; + + case CROSSING_END: + handleCrossingEnd(event); + break; + + case STATISTICS_UPDATE: + handleStatisticsUpdate(); + break; + + default: + System.err.println("Unknown event type: " + event.getType()); + } + } + + /** + * Handles vehicle generation event - creates a new vehicle and routes it. + */ + private void handleVehicleGeneration() { + Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime); + + System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n", + currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute()); + + // Register with statistics collector + statisticsCollector.recordVehicleGeneration(vehicle, currentTime); + + // Schedule arrival at first intersection + String firstIntersection = vehicle.getCurrentDestination(); + if (firstIntersection != null && !firstIntersection.equals("S")) { + // Assume minimal travel time to first intersection (e.g., 1-3 seconds) + double arrivalTime = currentTime + 1.0 + Math.random() * 2.0; + Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, firstIntersection); + eventQueue.offer(arrivalEvent); + } + + // Schedule next vehicle generation + scheduleNextVehicleGeneration(currentTime); + } + + /** + * Handles vehicle arrival at an intersection. + */ + private void handleVehicleArrival(Event event) { + Vehicle vehicle = (Vehicle) event.getData(); + String intersectionId = event.getLocation(); + + Intersection intersection = intersections.get(intersectionId); + if (intersection == null) { + System.err.println("Unknown intersection: " + intersectionId); + return; + } + + System.out.printf("[t=%.2f] Vehicle %s arrived at %s%n", + currentTime, vehicle.getId(), intersectionId); + + // Record arrival time for waiting time calculation + statisticsCollector.recordVehicleArrival(vehicle, intersectionId, currentTime); + + // Move vehicle to next destination before routing + // (it has now arrived at the current destination, so advance to next) + boolean hasNext = vehicle.advanceRoute(); + + if (!hasNext) { + // Vehicle reached its final destination + handleVehicleExit(vehicle); + return; + } + + String nextDestination = vehicle.getCurrentDestination(); + if (nextDestination == null || "S".equals(nextDestination)) { + handleVehicleExit(vehicle); + return; + } + + // Add vehicle to appropriate queue based on next destination + intersection.receiveVehicle(vehicle); + + // Try to process the vehicle immediately if light is green + tryProcessVehicle(vehicle, intersection); + } + + /** + * Attempts to process a vehicle at an intersection if conditions allow. + */ + private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { + String direction = intersection.getTrafficLights().stream() + .filter(tl -> tl.getQueueSize() > 0) + .map(TrafficLight::getDirection) + .findFirst() + .orElse(null); + + if (direction != null) { + TrafficLight light = intersection.getTrafficLight(direction); + if (light != null && light.getState() == TrafficLightState.GREEN) { + Vehicle v = light.removeVehicle(); + if (v != null) { + scheduleCrossing(v, intersection); + } + } + } + } + + /** + * Schedules a vehicle crossing event. + */ + private void scheduleCrossing(Vehicle vehicle, Intersection intersection) { + double waitTime = currentTime - statisticsCollector.getArrivalTime(vehicle); + vehicle.addWaitingTime(waitTime); + + // Schedule crossing start + Event crossingStart = new Event(currentTime, EventType.CROSSING_START, vehicle, intersection.getId()); + processEvent(crossingStart); + } + + /** + * Handles the start of a vehicle crossing. + */ + private void handleCrossingStart(Event event) { + Vehicle vehicle = (Vehicle) event.getData(); + String intersectionId = event.getLocation(); + + double crossingTime = getCrossingTime(vehicle.getType()); + + System.out.printf("[t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n", + currentTime, vehicle.getId(), intersectionId, crossingTime); + + // Schedule crossing end + double endTime = currentTime + crossingTime; + Event crossingEnd = new Event(endTime, EventType.CROSSING_END, vehicle, intersectionId); + eventQueue.offer(crossingEnd); + } + + /** + * Handles the end of a vehicle crossing. + */ + private void handleCrossingEnd(Event event) { + Vehicle vehicle = (Vehicle) event.getData(); + String intersectionId = event.getLocation(); + + Intersection intersection = intersections.get(intersectionId); + if (intersection != null) { + intersection.incrementVehiclesSent(); + } + + double crossingTime = getCrossingTime(vehicle.getType()); + vehicle.addCrossingTime(crossingTime); + + System.out.printf("[t=%.2f] Vehicle %s finished crossing at %s%n", + currentTime, vehicle.getId(), intersectionId); + + // Check next destination + String nextDest = vehicle.getCurrentDestination(); + if (nextDest != null && !nextDest.equals("S")) { + // Schedule arrival at next intersection + double travelTime = 5.0 + Math.random() * 5.0; // 5-10 seconds between intersections + double arrivalTime = currentTime + travelTime; + Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, nextDest); + eventQueue.offer(arrivalEvent); + } else { + // Vehicle reached exit + handleVehicleExit(vehicle); + } + } + + /** + * Handles a vehicle reaching the exit. + */ + private void handleVehicleExit(Vehicle vehicle) { + System.out.printf("[t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n", + currentTime, vehicle.getId(), + vehicle.getTotalWaitingTime(), + vehicle.getTotalTravelTime(currentTime)); + + statisticsCollector.recordVehicleExit(vehicle, currentTime); + } + + /** + * Handles traffic light state change. + */ + private void handleTrafficLightChange(Event event) { + TrafficLight light = (TrafficLight) event.getData(); + String intersectionId = event.getLocation(); + + // Toggle state + TrafficLightState newState = (light.getState() == TrafficLightState.RED) + ? TrafficLightState.GREEN + : TrafficLightState.RED; + + light.changeState(newState); + + System.out.printf("[t=%.2f] Traffic light %s changed to %s%n", + currentTime, light.getId(), newState); + + // If changed to GREEN, process waiting vehicles + if (newState == TrafficLightState.GREEN) { + Intersection intersection = intersections.get(intersectionId); + if (intersection != null) { + processGreenLight(light, intersection); + } + } + + // Schedule next state change + double nextChangeDelay = (newState == TrafficLightState.GREEN) + ? light.getGreenTime() + : light.getRedTime(); + + scheduleTrafficLightChange(light, intersectionId, nextChangeDelay); + } + + /** + * Processes vehicles when a light turns green. + */ + private void processGreenLight(TrafficLight light, Intersection intersection) { + while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { + Vehicle vehicle = light.removeVehicle(); + if (vehicle != null) { + scheduleCrossing(vehicle, intersection); + } + } + } + + /** + * Handles periodic statistics updates. + */ + private void handleStatisticsUpdate() { + System.out.printf("\n=== Statistics at t=%.2f ===%n", currentTime); + statisticsCollector.printCurrentStatistics(intersections, currentTime); + System.out.println(); + } + + /** + * Gets the crossing time for a vehicle type. + */ + private double getCrossingTime(VehicleType type) { + switch (type) { + case BIKE: + return config.getBikeVehicleCrossingTime(); + case LIGHT: + return config.getLightVehicleCrossingTime(); + case HEAVY: + return config.getHeavyVehicleCrossingTime(); + default: + return 2.0; + } + } + + /** + * Prints final simulation statistics. + */ + private void printFinalStatistics() { + System.out.println("\n" + "=".repeat(60)); + System.out.println("FINAL SIMULATION STATISTICS"); + System.out.println("=".repeat(60)); + + statisticsCollector.printFinalStatistics(intersections, currentTime); + + System.out.println("=".repeat(60)); + } + + public double getCurrentTime() { + return currentTime; + } + + public Map getIntersections() { + return new HashMap<>(intersections); + } + + public StatisticsCollector getStatisticsCollector() { + return statisticsCollector; + } +} diff --git a/main/src/main/java/sd/util/StatisticsCollector.java b/main/src/main/java/sd/util/StatisticsCollector.java new file mode 100644 index 0000000..b92d307 --- /dev/null +++ b/main/src/main/java/sd/util/StatisticsCollector.java @@ -0,0 +1,266 @@ +package sd.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import sd.config.SimulationConfig; +import sd.model.Intersection; +import sd.model.Vehicle; +import sd.model.VehicleType; + +/** + * Collects and manages statistics throughout the simulation. + * + * Tracks vehicle metrics, intersection performance, and system-wide statistics. + */ +public class StatisticsCollector { + + // Vehicle tracking + private final Map vehicleArrivalTimes; // vehicleId -> arrival time at current intersection + private final Map> vehicleIntersectionHistory; // vehicleId -> list of intersections visited + + // Overall statistics + private int totalVehiclesGenerated; + private int totalVehiclesCompleted; + private double totalSystemTime; // Sum of all vehicle travel times + private double totalWaitingTime; // Sum of all vehicle waiting times + + // Vehicle type statistics + private final Map vehicleTypeCount; + private final Map vehicleTypeWaitTime; + + // Per-intersection statistics + private final Map intersectionStats; + + public StatisticsCollector(SimulationConfig config) { + this.vehicleArrivalTimes = new HashMap<>(); + this.vehicleIntersectionHistory = new HashMap<>(); + this.totalVehiclesGenerated = 0; + this.totalVehiclesCompleted = 0; + this.totalSystemTime = 0.0; + this.totalWaitingTime = 0.0; + this.vehicleTypeCount = new HashMap<>(); + this.vehicleTypeWaitTime = new HashMap<>(); + this.intersectionStats = new HashMap<>(); + + // Initialize vehicle type counters + for (VehicleType type : VehicleType.values()) { + vehicleTypeCount.put(type, 0); + vehicleTypeWaitTime.put(type, 0.0); + } + } + + /** + * Records when a vehicle is generated. + */ + public void recordVehicleGeneration(Vehicle vehicle, double currentTime) { + totalVehiclesGenerated++; + + // Track vehicle type + VehicleType type = vehicle.getType(); + vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1); + + // Initialize intersection history + vehicleIntersectionHistory.put(vehicle.getId(), new ArrayList<>()); + } + + /** + * Records when a vehicle arrives at an intersection. + */ + public void recordVehicleArrival(Vehicle vehicle, String intersectionId, double currentTime) { + // Store arrival time for waiting time calculation + vehicleArrivalTimes.put(vehicle.getId(), currentTime); + + // Track intersection history + List history = vehicleIntersectionHistory.get(vehicle.getId()); + if (history != null) { + history.add(intersectionId); + } + + // Update intersection statistics + getOrCreateIntersectionStats(intersectionId).recordArrival(); + } + + /** + * Records when a vehicle exits the system. + */ + public void recordVehicleExit(Vehicle vehicle, double currentTime) { + totalVehiclesCompleted++; + + // Calculate total system time + double systemTime = vehicle.getTotalTravelTime(currentTime); + totalSystemTime += systemTime; + + // Track waiting time by vehicle type + double waitTime = vehicle.getTotalWaitingTime(); + totalWaitingTime += waitTime; + + VehicleType type = vehicle.getType(); + vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime); + + // Clean up tracking maps + vehicleArrivalTimes.remove(vehicle.getId()); + vehicleIntersectionHistory.remove(vehicle.getId()); + } + + /** + * Gets the arrival time of a vehicle at its current intersection. + */ + public double getArrivalTime(Vehicle vehicle) { + return vehicleArrivalTimes.getOrDefault(vehicle.getId(), 0.0); + } + + /** + * Prints current simulation statistics. + */ + public void printCurrentStatistics(Map intersections, double currentTime) { + System.out.printf("Vehicles: Generated=%d, Completed=%d, In-System=%d%n", + totalVehiclesGenerated, + totalVehiclesCompleted, + totalVehiclesGenerated - totalVehiclesCompleted); + + if (totalVehiclesCompleted > 0) { + System.out.printf("Average System Time: %.2fs%n", totalSystemTime / totalVehiclesCompleted); + System.out.printf("Average Waiting Time: %.2fs%n", totalWaitingTime / totalVehiclesCompleted); + } + + // Print per-intersection queue sizes + System.out.println("\nIntersection Queues:"); + for (Map.Entry entry : intersections.entrySet()) { + String id = entry.getKey(); + Intersection intersection = entry.getValue(); + System.out.printf(" %s: Queue=%d, Received=%d, Sent=%d%n", + id, + intersection.getTotalQueueSize(), + intersection.getTotalVehiclesReceived(), + intersection.getTotalVehiclesSent()); + } + } + + /** + * Prints final simulation statistics. + */ + public void printFinalStatistics(Map intersections, double currentTime) { + System.out.println("\nSIMULATION SUMMARY:"); + System.out.printf("Duration: %.2f seconds%n", currentTime); + System.out.printf("Total Vehicles Generated: %d%n", totalVehiclesGenerated); + System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesCompleted); + System.out.printf("Vehicles Still in System: %d%n", totalVehiclesGenerated - totalVehiclesCompleted); + + // Overall averages + if (totalVehiclesCompleted > 0) { + System.out.printf("%nAVERAGE METRICS:%n"); + System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesCompleted); + System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesCompleted); + System.out.printf(" Throughput: %.2f vehicles/second%n", totalVehiclesCompleted / currentTime); + } + + // Vehicle type breakdown + System.out.println("\nVEHICLE TYPE DISTRIBUTION:"); + for (VehicleType type : VehicleType.values()) { + int count = vehicleTypeCount.get(type); + if (count > 0) { + double percentage = (count * 100.0) / totalVehiclesGenerated; + double avgWait = vehicleTypeWaitTime.get(type) / count; + System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n", + type, count, percentage, avgWait); + } + } + + // Per-intersection statistics + System.out.println("\nINTERSECTION STATISTICS:"); + for (Map.Entry entry : intersections.entrySet()) { + String id = entry.getKey(); + Intersection intersection = entry.getValue(); + + System.out.printf(" %s:%n", id); + System.out.printf(" Vehicles Received: %d%n", intersection.getTotalVehiclesReceived()); + System.out.printf(" Vehicles Sent: %d%n", intersection.getTotalVehiclesSent()); + System.out.printf(" Current Queue Size: %d%n", intersection.getTotalQueueSize()); + + // Traffic light details + intersection.getTrafficLights().forEach(light -> { + System.out.printf(" Light %s: State=%s, Queue=%d, Processed=%d%n", + light.getDirection(), + light.getState(), + light.getQueueSize(), + light.getTotalVehiclesProcessed()); + }); + } + + // System health indicators + System.out.println("\nSYSTEM HEALTH:"); + int totalQueuedVehicles = intersections.values().stream() + .mapToInt(Intersection::getTotalQueueSize) + .sum(); + System.out.printf(" Total Queued Vehicles: %d%n", totalQueuedVehicles); + + if (totalVehiclesGenerated > 0) { + double completionRate = (totalVehiclesCompleted * 100.0) / totalVehiclesGenerated; + System.out.printf(" Completion Rate: %.1f%%%n", completionRate); + } + } + + /** + * Gets or creates intersection statistics object. + */ + private IntersectionStats getOrCreateIntersectionStats(String intersectionId) { + return intersectionStats.computeIfAbsent(intersectionId, k -> new IntersectionStats()); + } + + /** + * Inner class to track per-intersection statistics. + */ + private static class IntersectionStats { + private int totalArrivals; + private int totalDepartures; + + public IntersectionStats() { + this.totalArrivals = 0; + this.totalDepartures = 0; + } + + public void recordArrival() { + totalArrivals++; + } + + public void recordDeparture() { + totalDepartures++; + } + + public int getTotalArrivals() { + return totalArrivals; + } + + public int getTotalDepartures() { + return totalDepartures; + } + } + + // Getters + public int getTotalVehiclesGenerated() { + return totalVehiclesGenerated; + } + + public int getTotalVehiclesCompleted() { + return totalVehiclesCompleted; + } + + public double getTotalSystemTime() { + return totalSystemTime; + } + + public double getTotalWaitingTime() { + return totalWaitingTime; + } + + public double getAverageSystemTime() { + return totalVehiclesCompleted > 0 ? totalSystemTime / totalVehiclesCompleted : 0.0; + } + + public double getAverageWaitingTime() { + return totalVehiclesCompleted > 0 ? totalWaitingTime / totalVehiclesCompleted : 0.0; + } +} diff --git a/main/src/main/java/sd/util/VehicleGenerator.java b/main/src/main/java/sd/util/VehicleGenerator.java new file mode 100644 index 0000000..44e47b9 --- /dev/null +++ b/main/src/main/java/sd/util/VehicleGenerator.java @@ -0,0 +1,201 @@ +package sd.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import sd.config.SimulationConfig; +import sd.model.Vehicle; +import sd.model.VehicleType; + +/** + * Generates vehicles for the simulation using different arrival models. + * + * Supports two models: + * - POISSON: Exponentially distributed inter-arrival times + * - FIXED: Constant interval between arrivals + */ +public class VehicleGenerator { + + private final SimulationConfig config; + private final String arrivalModel; + private final double arrivalRate; + private final double fixedInterval; + + // Routes organized by entry point + private final List e1Routes; + private final List e2Routes; + private final List e3Routes; + + public VehicleGenerator(SimulationConfig config) { + this.config = config; + this.arrivalModel = config.getArrivalModel(); + this.arrivalRate = config.getArrivalRate(); + this.fixedInterval = config.getFixedArrivalInterval(); + + // Initialize routes for each entry point + this.e1Routes = new ArrayList<>(); + this.e2Routes = new ArrayList<>(); + this.e3Routes = new ArrayList<>(); + initializePossibleRoutes(); + } + + /** + * Defines routes that vehicles can take through the network based on entry point. + * + * Vehicles from E1 (34%, 33%, 33%): + * - E1→Cr1→Cr4→Cr5→S (34%) + * - E1→Cr1→Cr2→Cr5→S (33%) + * - E1→Cr1→Cr2→Cr3→S (33%) + * + * Vehicles from E2 (34%, 33%, 33%): + * - E2→Cr2→Cr5→S (34%) + * - E2→Cr2→Cr3→S (33%) + * - E2→Cr2→Cr1→Cr4→Cr5→S (33%) + * + * Vehicles from E3 (34%, 33%, 33%): + * - E3→Cr3→S (34%) + * - E3→Cr3→Cr2→Cr5→S (33%) + * - E3→Cr3→Cr2→Cr1→Cr4→Cr5→S (33%) + */ + private void initializePossibleRoutes() { + // E1 routes + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34)); + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33)); + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33)); + + // E2 routes + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr5", "S"), 0.34)); + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr3", "S"), 0.33)); + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); + + // E3 routes + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "S"), 0.34)); + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33)); + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); + } + + /** + * Calculates the time of the next vehicle arrival based on the configured model. + * + * @param currentTime the current simulation time + * @return the absolute time when the next vehicle should arrive + */ + public double getNextArrivalTime(double currentTime) { + if ("POISSON".equalsIgnoreCase(arrivalModel)) { + // Exponential distribution (Poisson process) + double interval = RandomGenerator.generateExponentialInterval(arrivalRate); + return currentTime + interval; + } else { + // Fixed interval + return currentTime + fixedInterval; + } + } + + /** + * Generates a new vehicle with random type and route. + * + * @param vehicleId unique identifier for the vehicle + * @param entryTime time when the vehicle enters the system + * @return a new Vehicle object + */ + public Vehicle generateVehicle(String vehicleId, double entryTime) { + VehicleType type = selectVehicleType(); + List route = selectRandomRoute(); + + return new Vehicle(vehicleId, type, entryTime, route); + } + + /** + * Selects a vehicle type based on configured probabilities. + * + * @return the selected vehicle type + */ + private VehicleType selectVehicleType() { + double bikeProbability = config.getBikeVehicleProbability(); + double lightProbability = config.getLightVehicleProbability(); + double heavyProbability = config.getHeavyVehicleProbability(); + + // Normalize probabilities in case they don't sum to exactly 1.0 + double total = bikeProbability + lightProbability + heavyProbability; + bikeProbability /= total; + lightProbability /= total; + + double rand = Math.random(); + + if (rand < bikeProbability) { + return VehicleType.BIKE; + } else if (rand < bikeProbability + lightProbability) { + return VehicleType.LIGHT; + } else { + return VehicleType.HEAVY; + } + } + + /** + * Selects a random entry point and then selects a route based on probabilities. + * + * @return a list of intersection IDs representing the route + */ + private List selectRandomRoute() { + // Randomly select an entry point (E1, E2, or E3 with equal probability) + double entryRandom = Math.random(); + List selectedRoutes; + + if (entryRandom < 0.333) { + selectedRoutes = e1Routes; + } else if (entryRandom < 0.666) { + selectedRoutes = e2Routes; + } else { + selectedRoutes = e3Routes; + } + + // Select route based on cumulative probabilities + double rand = Math.random(); + double cumulative = 0.0; + + for (RouteWithProbability routeWithProb : selectedRoutes) { + cumulative += routeWithProb.probability; + if (rand <= cumulative) { + return new ArrayList<>(routeWithProb.route); + } + } + + // Fallback (should not reach here) + return new ArrayList<>(selectedRoutes.get(0).route); + } + + /** + * Returns information about the generator configuration. + */ + public String getInfo() { + int totalRoutes = e1Routes.size() + e2Routes.size() + e3Routes.size(); + return String.format( + "VehicleGenerator{model=%s, rate=%.2f, interval=%.2f, routes=%d (E1:%d, E2:%d, E3:%d)}", + arrivalModel, arrivalRate, fixedInterval, totalRoutes, + e1Routes.size(), e2Routes.size(), e3Routes.size() + ); + } + + /** + * Inner class to hold a route with its probability. + */ + private static class RouteWithProbability { + final List route; + final double probability; + + RouteWithProbability(List route, double probability) { + this.route = route; + this.probability = probability; + } + } +} diff --git a/main/src/test/java/SimulationTest.java b/main/src/test/java/SimulationTest.java new file mode 100644 index 0000000..948dee8 --- /dev/null +++ b/main/src/test/java/SimulationTest.java @@ -0,0 +1,127 @@ +package sd; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +import sd.config.SimulationConfig; +import sd.engine.SimulationEngine; +import sd.model.Event; +import sd.model.EventType; +import sd.model.Intersection; +import sd.model.TrafficLight; +import sd.model.TrafficLightState; +import sd.model.Vehicle; +import sd.model.VehicleType; +import sd.util.StatisticsCollector; +import sd.util.VehicleGenerator; + +/** + * Basic tests for the simulation components. + */ +class SimulationTest { + + @Test + void testConfigurationLoading() throws IOException { + SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); + + assertEquals(60.0, config.getSimulationDuration()); + assertEquals("POISSON", config.getArrivalModel()); + assertEquals(0.5, config.getArrivalRate()); + assertEquals(10.0, config.getStatisticsUpdateInterval()); + } + + @Test + void testVehicleGeneration() throws IOException { + SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); + VehicleGenerator generator = new VehicleGenerator(config); + + Vehicle vehicle = generator.generateVehicle("TEST1", 0.0); + + assertNotNull(vehicle); + assertEquals("TEST1", vehicle.getId()); + assertNotNull(vehicle.getType()); + assertNotNull(vehicle.getRoute()); + assertTrue(vehicle.getRoute().size() > 0); + } + + @Test + void testEventOrdering() { + Event e1 = new Event(5.0, EventType.VEHICLE_ARRIVAL, null, "Cr1"); + Event e2 = new Event(3.0, EventType.VEHICLE_ARRIVAL, null, "Cr2"); + Event e3 = new Event(7.0, EventType.TRAFFIC_LIGHT_CHANGE, null, "Cr1"); + + assertTrue(e2.compareTo(e1) < 0); // e2 should come before e1 + assertTrue(e1.compareTo(e3) < 0); // e1 should come before e3 + } + + @Test + void testIntersectionVehicleQueue() { + Intersection intersection = new Intersection("TestCr"); + TrafficLight light = new TrafficLight("TestCr-N", "North", 30.0, 30.0); + + intersection.addTrafficLight(light); + + Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0, + java.util.Arrays.asList("TestCr", "S")); + + intersection.configureRoute("S", "North"); + + // Advance route to next destination + v1.advanceRoute(); + + intersection.receiveVehicle(v1); + + assertEquals(1, intersection.getTotalQueueSize()); + assertEquals(1, intersection.getTotalVehiclesReceived()); + } + + @Test + void testTrafficLightStateChange() { + TrafficLight light = new TrafficLight("Test-Light", "North", 30.0, 30.0); + + assertEquals(TrafficLightState.RED, light.getState()); + + light.changeState(TrafficLightState.GREEN); + assertEquals(TrafficLightState.GREEN, light.getState()); + + light.changeState(TrafficLightState.RED); + assertEquals(TrafficLightState.RED, light.getState()); + } + + @Test + void testSimulationEngineInitialization() throws IOException { + SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); + SimulationEngine engine = new SimulationEngine(config); + + engine.initialize(); + + assertNotNull(engine.getIntersections()); + assertEquals(5, engine.getIntersections().size()); + + // Check that intersections have traffic lights + for (Intersection intersection : engine.getIntersections().values()) { + assertEquals(4, intersection.getTrafficLights().size()); // North, South, East, West + } + } + + @Test + void testStatisticsCollector() throws IOException { + SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); + StatisticsCollector collector = new StatisticsCollector(config); + + Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0, + java.util.Arrays.asList("Cr1", "Cr2", "S")); + + collector.recordVehicleGeneration(v1, 0.0); + assertEquals(1, collector.getTotalVehiclesGenerated()); + + collector.recordVehicleArrival(v1, "Cr1", 1.0); + + collector.recordVehicleExit(v1, 10.0); + assertEquals(1, collector.getTotalVehiclesCompleted()); + } +}