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());
+ }
+}