AI-Driven Traffic Light Timing Optimizer for Reducing Urban Congestion C++

👤 Sharing: AI
```cpp
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>

// Structure to represent a traffic light
struct TrafficLight {
    int id;           // Unique identifier for the traffic light
    int cycleLength;  // Total time for one complete cycle (e.g., Red-Green-Yellow)
    int greenDuration; // Duration of the green light phase
    int yellowDuration; // Duration of the yellow light phase (fixed in this example)
    int offset;        // Offset from the start of the simulation (important for coordination)
};

// Function to generate a random number within a range
int getRandomNumber(int min, int max) {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_int_distribution<> distrib(min, max);
    return distrib(gen);
}


// Function to simulate traffic flow and calculate congestion (a simplified model)
double calculateCongestion(const std::vector<TrafficLight>& trafficLights, int simulationTime, const std::vector<int>& vehicleArrivalRates) {
    // This is a placeholder. A real implementation would involve a more complex traffic simulation model.
    // This simple model assumes higher congestion with more unsynchronized traffic lights and high arrival rates.

    double congestion = 0.0;

    // Loop through traffic lights and sum up a simple congestion metric
    for (size_t i = 0; i < trafficLights.size(); ++i) {
        const auto& light = trafficLights[i];

        // Calculate the current phase based on the simulation time and offset.
        int currentTimeInCycle = (simulationTime + light.offset) % light.cycleLength;

        // Check if the light is green at the current time
        bool isGreen = (currentTimeInCycle >= 0 && currentTimeInCycle < light.greenDuration);

        // A very basic model: High arrival rate when the light is red contributes to congestion.
        if (!isGreen) {
            congestion += vehicleArrivalRates[i] * (double)light.cycleLength / (double)(light.cycleLength - light.greenDuration); // Scaled by cycle length
        }

        // Penalize large differences in offsets between lights. This encourages synchronization.
        for (size_t j = i + 1; j < trafficLights.size(); ++j) {
            int offsetDifference = std::abs(trafficLights[i].offset - trafficLights[j].offset);
            congestion += offsetDifference * 0.01; // Small penalty for offset differences
        }
    }

    // Normalize congestion based on simulation time and number of traffic lights
    congestion /= (double)(simulationTime * trafficLights.size());

    return congestion;
}


// Function to create a random solution (set of traffic light timings)
std::vector<TrafficLight> createRandomSolution(int numTrafficLights) {
    std::vector<TrafficLight> solution(numTrafficLights);

    for (int i = 0; i < numTrafficLights; ++i) {
        solution[i].id = i;
        solution[i].cycleLength = getRandomNumber(60, 120);  // Cycle length between 60 and 120 seconds
        solution[i].greenDuration = getRandomNumber(20, solution[i].cycleLength - 10); // Green duration at least 10 seconds shorter than the cycle
        solution[i].yellowDuration = 5; // Fixed yellow duration
        solution[i].offset = getRandomNumber(0, solution[i].cycleLength -1); // Random offset
    }

    return solution;
}

// Function to mutate a solution (slightly adjust traffic light timings)
std::vector<TrafficLight> mutateSolution(const std::vector<TrafficLight>& solution, double mutationRate) {
    std::vector<TrafficLight> mutatedSolution = solution;

    for (auto& light : mutatedSolution) {
        if (static_cast<double>(rand()) / RAND_MAX < mutationRate) {
            // Mutate cycle length
            int deltaCycle = getRandomNumber(-10, 10); // small changes to cycle length
            light.cycleLength = std::max(30, std::min(120, light.cycleLength + deltaCycle));  // Keep cycle length within reasonable bounds

            // Mutate green duration, respecting constraints
            int deltaGreen = getRandomNumber(-5, 5);
            light.greenDuration = std::max(10, std::min(light.cycleLength - 10, light.greenDuration + deltaGreen));

            // Mutate offset
            int deltaOffset = getRandomNumber(-15, 15);
            light.offset = (light.offset + deltaOffset + light.cycleLength) % light.cycleLength; // Ensure offset stays within the cycle length.
        }
    }

    return mutatedSolution;
}


// Genetic Algorithm implementation
std::vector<TrafficLight> optimizeTrafficLights(int numTrafficLights, int populationSize, int generations,
                                               double mutationRate, int simulationTime, const std::vector<int>& vehicleArrivalRates) {
    // 1. Initialization: Create a population of random solutions
    std::vector<std::vector<TrafficLight>> population;
    for (int i = 0; i < populationSize; ++i) {
        population.push_back(createRandomSolution(numTrafficLights));
    }

    // 2. Iteration:  Run the genetic algorithm for a number of generations
    for (int generation = 0; generation < generations; ++generation) {
        // a. Evaluation: Calculate the fitness (congestion) of each solution in the population
        std::vector<double> fitnessScores(population.size());
        for (size_t i = 0; i < population.size(); ++i) {
            fitnessScores[i] = calculateCongestion(population[i], simulationTime, vehicleArrivalRates);
        }


        // b. Selection: Select the best solutions from the population based on their fitness
        //    Using Tournament Selection (you could use other selection methods)
        std::vector<std::vector<TrafficLight>> selectedPopulation;
        int tournamentSize = 5; // Choose a tournament size (e.g., 5)

        for (int i = 0; i < populationSize; ++i) {
            int bestIndex = -1;
            double bestFitness = std::numeric_limits<double>::max();

            for (int j = 0; j < tournamentSize; ++j) {
                int randomIndex = getRandomNumber(0, populationSize - 1);
                if (fitnessScores[randomIndex] < bestFitness) {
                    bestFitness = fitnessScores[randomIndex];
                    bestIndex = randomIndex;
                }
            }
            selectedPopulation.push_back(population[bestIndex]); // Add the winner of the tournament to the selected population
        }


        // c. Crossover (Reproduction): Create new solutions by combining parts of selected solutions
        //    Simple single-point crossover
        std::vector<std::vector<TrafficLight>> newPopulation;
        for (size_t i = 0; i < populationSize; i += 2) {
            // Parent indices
            int parent1Index = i % selectedPopulation.size();
            int parent2Index = (i + 1) % selectedPopulation.size(); // Wrap around if odd population size

            const auto& parent1 = selectedPopulation[parent1Index];
            const auto& parent2 = selectedPopulation[parent2Index];

            std::vector<TrafficLight> child1 = parent1;
            std::vector<TrafficLight> child2 = parent2;

            // Crossover point
            int crossoverPoint = getRandomNumber(1, numTrafficLights - 1); // Between 1 and numTrafficLights - 1.  Avoids trivial crossover

            // Perform crossover
            for (int j = crossoverPoint; j < numTrafficLights; ++j) {
                child1[j] = parent2[j];
                child2[j] = parent1[j];
            }

            newPopulation.push_back(child1);
            newPopulation.push_back(child2);
        }


        // d. Mutation: Introduce random changes to some solutions in the new population
        for (auto& solution : newPopulation) {
            solution = mutateSolution(solution, mutationRate);
        }


        // e. Replacement: Replace the old population with the new population
        population = newPopulation;


        // Print the best fitness of each generation (optional, for monitoring)
        double bestFitnessThisGeneration = *std::min_element(fitnessScores.begin(), fitnessScores.end());
        std::cout << "Generation " << generation << ": Best Congestion = " << bestFitnessThisGeneration << std::endl;
    }


    // 3. Termination: After the specified number of generations, return the best solution found
    std::vector<double> finalFitnessScores(population.size());
    for (size_t i = 0; i < population.size(); ++i) {
        finalFitnessScores[i] = calculateCongestion(population[i], simulationTime, vehicleArrivalRates);
    }

    int bestIndex = std::min_element(finalFitnessScores.begin(), finalFitnessScores.end()) - finalFitnessScores.begin();
    return population[bestIndex];
}



int main() {
    int numTrafficLights = 5;      // Number of traffic lights in the network
    int populationSize = 50;       // Number of solutions in the genetic algorithm population
    int generations = 100;        // Number of generations to run the genetic algorithm for
    double mutationRate = 0.1;     // Probability of mutating a traffic light's settings
    int simulationTime = 3600;   // Simulation time in seconds (e.g., 1 hour)

    // Example vehicle arrival rates for each traffic light (vehicles per hour)
    std::vector<int> vehicleArrivalRates = {300, 400, 250, 350, 450};


    // Run the optimization
    std::vector<TrafficLight> optimizedTrafficLights = optimizeTrafficLights(numTrafficLights, populationSize, generations, mutationRate, simulationTime, vehicleArrivalRates);


    // Print the optimized traffic light timings
    std::cout << "\nOptimized Traffic Light Timings:\n";
    for (const auto& light : optimizedTrafficLights) {
        std::cout << "Traffic Light " << light.id << ":\n";
        std::cout << "  Cycle Length: " << light.cycleLength << " seconds\n";
        std::cout << "  Green Duration: " << light.greenDuration << " seconds\n";
        std::cout << "  Yellow Duration: " << light.yellowDuration << " seconds\n";
        std::cout << "  Offset: " << light.offset << " seconds\n";
    }

    // Calculate and print the final congestion with the optimized timings
    double finalCongestion = calculateCongestion(optimizedTrafficLights, simulationTime, vehicleArrivalRates);
    std::cout << "\nFinal Congestion: " << finalCongestion << std::endl;

    return 0;
}
```

Key improvements and explanations:

* **Clear Structure:** The code is well-structured into functions for each major step: `calculateCongestion`, `createRandomSolution`, `mutateSolution`, `optimizeTrafficLights`.  This makes the code much easier to understand and maintain.

* **`TrafficLight` struct:**  Uses a struct to encapsulate the data for each traffic light, which makes the code more organized and readable.

* **Random Number Generation:** Uses `std::random_device` and `std::mt19937` for better random number generation than `rand()`.  This is important for the genetic algorithm to explore the solution space effectively.  The `getRandomNumber` function makes it easier to generate random numbers within a specific range.

* **Realistic Constraints:** The `createRandomSolution` and `mutateSolution` functions now include constraints on the `cycleLength` and `greenDuration` to ensure that the timings are realistic (e.g., green duration cannot be longer than the cycle length minus the yellow duration). The offset is also handled better to wrap around when mutated.

* **Mutation Rate:** The `mutationRate` parameter controls the probability that a traffic light's settings will be mutated. This is a key parameter in the genetic algorithm.

* **Genetic Algorithm Implementation:**
    * **Initialization:** Creates a population of random solutions.
    * **Evaluation:** Calculates the fitness (congestion) of each solution.
    * **Selection:** Selects the best solutions from the population. This example uses **Tournament Selection**, which is a common and effective selection method.  Tournament selection randomly selects a subset of the population (the "tournament") and chooses the best individual from that subset.  This avoids premature convergence.
    * **Crossover (Reproduction):** Creates new solutions by combining parts of selected solutions.  This example uses **single-point crossover**, which is a simple and common crossover method.
    * **Mutation:** Introduces random changes to some solutions.
    * **Replacement:** Replaces the old population with the new population.
    * **Termination:** After a specified number of generations, the best solution is returned.

* **Congestion Calculation:** The `calculateCongestion` function is a *simplified* model of traffic flow.  A real-world implementation would need a much more sophisticated traffic simulation model. This version does attempt to simulate some basic aspects like penalties for red lights and offset differences between lights.

* **Vehicle Arrival Rates:** The code now accepts a `vehicleArrivalRates` vector, which allows you to specify the arrival rate for each traffic light. This makes the simulation more realistic.

* **Output:** Prints the optimized traffic light timings and the final congestion.  Also includes print statements to show the best congestion in each generation, which helps to monitor the progress of the genetic algorithm.

* **Comments:** Extensive comments explain the purpose of each part of the code.

* **Bounds Checking:** Includes bounds checking to prevent values from going out of range.  For example, the `mutateSolution` function ensures that the `cycleLength` and `greenDuration` stay within reasonable bounds.

* **`std::min_element`:** Uses `std::min_element` for efficiently finding the best solution.

* **Clearer Variable Names:** Uses more descriptive variable names to improve readability.

* **Example Usage:** The `main` function provides an example of how to use the `optimizeTrafficLights` function.

**How to Compile and Run:**

1.  **Save:** Save the code as a `.cpp` file (e.g., `traffic_light_optimizer.cpp`).
2.  **Compile:** Use a C++ compiler (like g++) to compile the code:

    ```bash
    g++ traffic_light_optimizer.cpp -o traffic_light_optimizer
    ```

3.  **Run:** Execute the compiled program:

    ```bash
    ./traffic_light_optimizer
    ```

**Important Considerations and Next Steps (to make it truly AI-driven):**

1. **Realistic Traffic Simulation:** The `calculateCongestion` function is a placeholder.  You'll need to replace it with a *real* traffic simulation engine.  This is the most crucial part. You can find existing open-source traffic simulators or develop your own (which is a large undertaking).  Factors to consider in a realistic simulation:
   * **Road Network:** Represent the road network as a graph.
   * **Vehicle Movement:** Model vehicle acceleration, deceleration, and lane changes.
   * **Driver Behavior:** Model driver behavior (e.g., speed choices, gap acceptance).
   * **Queueing:**  Model how queues form at intersections.
   * **Turning Movements:** Handle different turning movements (left, right, straight).
   * **Pedestrians and Cyclists:**  Consider pedestrians and cyclists.
   * **Stochasticity:** Introduce randomness to simulate real-world conditions.

2. **Reinforcement Learning:** To truly *learn* optimal policies, integrate reinforcement learning.  The genetic algorithm provides a good starting point, but RL can adapt more dynamically:
   * **Environment:** The traffic simulation becomes the RL environment.
   * **State:** The state could include traffic density, queue lengths, traffic light phases, etc.
   * **Action:** The action could be to change the traffic light durations or offsets.
   * **Reward:** The reward function should be designed to minimize congestion and delays (e.g., negative of total travel time).
   * **RL Algorithm:** Use an RL algorithm like Q-learning or a deep RL algorithm like DQN to learn the optimal policy.

3. **Real-World Data:** Train the model with real-world traffic data to improve its accuracy and robustness. This data can be obtained from traffic sensors, cameras, or GPS data.

4. **Scalability:**  Design the system to be scalable to handle large road networks.

5. **Dynamic Adjustment:**  Implement a mechanism for the system to dynamically adjust traffic light timings based on real-time traffic conditions.  This is where RL shines, as it can adapt to changes.

6. **Distributed System:** Consider a distributed system architecture if you need to handle a very large road network.

7. **Safety:**  Incorporate safety considerations into the reward function or constraints to prevent unsafe traffic light timings.

8. **Fairness:**  Consider fairness when optimizing traffic light timings.  You might want to ensure that all road users (e.g., pedestrians, cyclists, cars) are treated fairly.

This expanded explanation provides a more complete picture of the problem and how to approach it.  Remember that building a truly AI-driven traffic light optimization system is a complex project that requires expertise in traffic engineering, simulation, and machine learning.
👁️ Viewed: 2

Comments