basic core function

This commit is contained in:
Leandro Afonso
2025-10-21 20:26:57 +01:00
parent 30fc2d6554
commit 1c033880e7
6 changed files with 1167 additions and 1 deletions

View File

@@ -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)");
}
}

View File

@@ -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<Event> eventQueue;
private final Map<String, Intersection> 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<String, Intersection> getIntersections() {
return new HashMap<>(intersections);
}
public StatisticsCollector getStatisticsCollector() {
return statisticsCollector;
}
}

View File

@@ -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<String, Double> vehicleArrivalTimes; // vehicleId -> arrival time at current intersection
private final Map<String, List<String>> 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<VehicleType, Integer> vehicleTypeCount;
private final Map<VehicleType, Double> vehicleTypeWaitTime;
// Per-intersection statistics
private final Map<String, IntersectionStats> 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<String> 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<String, Intersection> 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<String, Intersection> 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<String, Intersection> 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<String, Intersection> 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;
}
}

View File

@@ -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<RouteWithProbability> e1Routes;
private final List<RouteWithProbability> e2Routes;
private final List<RouteWithProbability> 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<String> 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<String> selectRandomRoute() {
// Randomly select an entry point (E1, E2, or E3 with equal probability)
double entryRandom = Math.random();
List<RouteWithProbability> 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<String> route;
final double probability;
RouteWithProbability(List<String> route, double probability) {
this.route = route;
this.probability = probability;
}
}
}

View File

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