Genetic Algorithm - CSU083 | Shoolini University

Genetic Algorithm

1. Prerequisites

To understand Genetic Algorithms, you need a foundational grasp of the following concepts:

2. What is a Genetic Algorithm?

Genetic Algorithm (GA) is an optimization technique inspired by the process of natural selection. It iteratively evolves a population of candidate solutions using mechanisms like selection, crossover, and mutation to find optimal or near-optimal solutions.

2.1 Key Components

2.2 Working Mechanism

  1. Initialize a population randomly.
  2. Evaluate fitness using the fitness function.
  3. Select individuals based on fitness.
  4. Apply crossover and mutation to generate new offspring.
  5. Repeat until the termination condition is met.

3. Why Does This Algorithm Exist?

Genetic Algorithms exist to solve complex optimization problems where traditional deterministic methods fail or are inefficient. They are particularly useful for:

3.1 Real-World Applications

4. When Should You Use It?

Genetic Algorithms are best used when:

4.1 When NOT to Use It?

5. How Does It Compare to Alternatives?

5.1 Strengths

5.2 Weaknesses

5.3 Comparison with Other Optimization Techniques

Algorithm Strengths Weaknesses
Genetic Algorithm (GA) Handles large search spaces, good for non-differentiable functions. Slow convergence, requires parameter tuning.
Gradient Descent Efficient for convex problems, guaranteed to find local minimum. Fails in non-convex landscapes.
Simulated Annealing Avoids local optima using probabilistic jumps. Slow convergence, parameter tuning required.
Particle Swarm Optimization (PSO) Inspired by swarm intelligence, simpler than GA. Can converge prematurely, lacks mutation-like diversity.

6. Basic Implementation

The following Python implementation demonstrates a simple Genetic Algorithm for optimizing the function:

$$ f(x) = x^2 $$

where x is represented as a binary chromosome.


import random

# Parameters
POP_SIZE = 6      # Population size
GENE_LENGTH = 5   # Chromosome length (5-bit binary)
MUTATION_RATE = 0.1
GENERATIONS = 5

# Fitness Function: f(x) = x^2
def fitness(chromosome):
    return int(chromosome, 2) ** 2

# Generate initial population (random binary strings)
def generate_population():
    return [bin(random.randint(0, 2**GENE_LENGTH - 1))[2:].zfill(GENE_LENGTH) for _ in range(POP_SIZE)]

# Selection: Roulette Wheel Selection
def select(population):
    total_fitness = sum(fitness(ch) for ch in population)
    pick = random.uniform(0, total_fitness)
    current = 0
    for ch in population:
        current += fitness(ch)
        if current >= pick:
            return ch
    return population[-1]

# Crossover: Single-point Crossover
def crossover(parent1, parent2):
    point = random.randint(1, GENE_LENGTH - 1)
    child1 = parent1[:point] + parent2[point:]
    child2 = parent2[:point] + parent1[point:]
    return child1, child2

# Mutation: Flip a random bit
def mutate(chromosome):
    if random.random() < MUTATION_RATE:
        idx = random.randint(0, GENE_LENGTH - 1)
        chromosome = chromosome[:idx] + ('0' if chromosome[idx] == '1' else '1') + chromosome[idx + 1:]
    return chromosome

# Genetic Algorithm Execution
def genetic_algorithm():
    population = generate_population()
    for gen in range(GENERATIONS):
        population = sorted(population, key=fitness, reverse=True)  # Sort by fitness
        new_population = []

        while len(new_population) < POP_SIZE:
            parent1 = select(population)
            parent2 = select(population)
            child1, child2 = crossover(parent1, parent2)
            new_population.extend([mutate(child1), mutate(child2)])

        population = new_population[:POP_SIZE]
        print(f"Generation {gen + 1}: Best = {population[0]} (x={int(population[0], 2)}, Fitness={fitness(population[0])})")

genetic_algorithm()

7. Dry Run

Let's assume an initial population of 6 individuals randomly generated as:

Chromosome (Binary) x (Decimal) Fitness (x²)
01100 12 144
10101 21 441
11010 26 676
00011 3 9
11100 28 784
01001 9 81

7.1 Generation 1

Selection: Higher fitness values have a higher chance of selection.

Crossover: Assume a crossover point at index 3:

Parent1: 11100
Parent2: 11010
--------------------------------
Child1: 11110
Child2: 11000

Mutation: Assume 11000 mutates at bit index 2:

Original: 11000
Mutated: 11100

New population after reproduction:

New Chromosome x Fitness
11110 30 900
11100 28 784
01100 12 144
10101 21 441
11010 26 676
01001 9 81

7.2 Generation 2

Observations

8. Time & Space Complexity Analysis

8.1 Time Complexity

The Genetic Algorithm (GA) consists of multiple components, each contributing to the overall complexity.

8.1.1 Breakdown of Time Complexity

8.1.2 Overall Complexity

The GA runs for T generations, so the total complexity is:

$$ O(T \cdot (P + G \cdot P + P \cdot G)) $$

Since G is typically small compared to P, we approximate:

$$ O(T \cdot P \cdot G) $$

8.1.3 Case-wise Analysis

9. Space Complexity Analysis

Space complexity depends on the representation of chromosomes, population storage, and auxiliary structures.

9.1 Breakdown of Space Complexity

9.2 Overall Space Complexity

The total space requirement is:

$$ O(P \cdot G) + O(P) + O(P) = O(P \cdot G) $$

9.3 Space Growth with Input Size

10. Understanding Trade-offs

10.1 Accuracy vs. Speed

10.2 Memory vs. Performance

10.3 Deterministic vs. Stochastic Trade-offs

11. Optimizations & Variants

11.1 Common Optimizations

Genetic Algorithms can be computationally expensive. The following optimizations improve efficiency:

11.1.1 Parallelization
11.1.2 Adaptive Mutation & Crossover
11.1.3 Elitism
11.1.4 Hybridization with Local Search

11.2 Variants of Genetic Algorithms

11.2.1 Steady-State GA
11.2.2 Multi-Objective Genetic Algorithm (MOGA)
11.2.3 Genetic Programming (GP)
11.2.4 Differential Evolution (DE)

12. Comparing Iterative vs. Recursive Implementations

12.1 Iterative Implementation (Standard Approach)

Most Genetic Algorithms are implemented iteratively due to:


def genetic_algorithm():
    population = generate_population()
    for gen in range(GENERATIONS):
        population = evolve_population(population)
        print(f"Generation {gen + 1}: Best = {best_solution(population)}")

12.2 Recursive Implementation (Alternative Approach)

Recursion is possible but has limitations:


def genetic_algorithm_recursive(population, generation=0):
    if generation >= GENERATIONS:
        return best_solution(population)
    new_population = evolve_population(population)
    return genetic_algorithm_recursive(new_population, generation + 1)

12.3 Efficiency Comparison

Aspect Iterative Recursive
Memory Usage O(P * G) O(T * P * G) (due to recursive stack)
Performance Faster, avoids function call overhead Slower due to stack operations
Readability Clearer, easier to debug Compact, but harder to track
Scalability More scalable for large generations Limited by recursion depth

12.4 When to Use Which?

13. Edge Cases & Failure Handling

Genetic Algorithms (GAs) are robust but can fail due to improper configuration, bad fitness functions, or population stagnation. Identifying edge cases and handling them ensures reliability.

13.1 Common Edge Cases

13.1.1 Premature Convergence
13.1.2 Loss of Genetic Diversity
13.1.3 Poor Fitness Function Design
13.1.4 Computational Overhead
13.1.5 Overfitting in Learning Problems

13.2 Handling Failures

14. Test Cases to Verify Correctness

To ensure correctness, test cases should cover different scenarios, including edge cases.

14.1 Basic Functionality Tests

14.1.1 Fitness Function Test

Check whether the fitness function correctly evaluates known inputs.


def test_fitness():
    assert fitness("00010") == 4  # 2² = 4
    assert fitness("00100") == 16 # 4² = 16
    print("Fitness function test passed.")
14.1.2 Selection Mechanism Test

Ensure individuals with higher fitness have a higher selection probability.


def test_selection():
    population = ["00010", "00100", "01000"]  # x = [2, 4, 8]
    selected = select(population)
    assert selected in population  # Ensuring a valid selection
    print("Selection test passed.")

14.2 Edge Case Tests

14.2.1 Convergence Test

Check if GA converges to an optimal solution.


def test_convergence():
    best_solution = genetic_algorithm()
    assert best_solution is not None
    print("Convergence test passed.")
14.2.2 Diversity Test

Ensure that mutation maintains diversity.


def test_diversity():
    population = ["00000", "00000", "00000"]
    mutated = mutate(population[0])
    assert mutated != "00000"
    print("Diversity test passed.")

15. Real-World Failure Scenarios

15.1 Failure in Financial Forecasting

Issue: A trading algorithm optimized using GA worked well in historical data but failed in live markets.

Reason: Overfitting to past trends, not adaptable to new market conditions.

Solution: Introduce randomness, retrain periodically, and use multi-objective optimization.

15.2 Failure in Robotics Path Planning

Issue: A GA-based robot navigation system optimized for simulation failed in real environments.

Reason: The fitness function did not account for sensor noise and real-world dynamics.

Solution: Modify fitness function to include real-world constraints.

15.3 Failure in Game AI

Issue: A GA-based AI in a game exploited an unintended loophole instead of playing fairly.

Reason: Poorly designed fitness function rewarded the wrong behavior.

Solution: Redesign fitness function to align with intended objectives.

16. Real-World Applications & Industry Use Cases

Genetic Algorithms (GA) are widely used across multiple industries for optimization, machine learning, and problem-solving.

16.1 Real-World Applications

16.1.1 Artificial Intelligence & Machine Learning
16.1.2 Robotics & Autonomous Systems
16.1.3 Financial Modeling
16.1.4 Bioinformatics & Healthcare
16.1.5 Engineering & Manufacturing

17. Open-Source Implementations

Several open-source Genetic Algorithm libraries provide optimized implementations.

17.1 Python Libraries

17.2 Other Languages

17.3 GitHub Repositories

18. Practical Project: Solving the Travelling Salesman Problem (TSP) with Genetic Algorithm

The Travelling Salesman Problem (TSP) requires finding the shortest route that visits all cities once and returns to the starting city. We use GA to find an approximate solution.

18.1 Python Implementation


import random
import numpy as np

# Number of cities and population size
CITIES = 10
POP_SIZE = 100
GENERATIONS = 200

# Generate random city coordinates
cities = np.random.rand(CITIES, 2)

# Distance function
def distance(city1, city2):
    return np.linalg.norm(city1 - city2)

# Fitness function: total route distance
def fitness(route):
    return sum(distance(cities[route[i]], cities[route[i+1]]) for i in range(CITIES - 1)) + distance(cities[route[-1]], cities[route[0]])

# Generate initial population
def generate_population():
    return [random.sample(range(CITIES), CITIES) for _ in range(POP_SIZE)]

# Selection: Tournament Selection
def select(population):
    return min(random.sample(population, 5), key=fitness)

# Crossover: Ordered Crossover
def crossover(parent1, parent2):
    start, end = sorted(random.sample(range(CITIES), 2))
    child = [-1] * CITIES
    child[start:end] = parent1[start:end]
    remaining = [gene for gene in parent2 if gene not in child]
    index = 0
    for i in range(CITIES):
        if child[i] == -1:
            child[i] = remaining[index]
            index += 1
    return child

# Mutation: Swap Mutation
def mutate(route):
    if random.random() < 0.2:
        i, j = random.sample(range(CITIES), 2)
        route[i], route[j] = route[j], route[i]
    return route

# Genetic Algorithm Execution
def genetic_algorithm():
    population = generate_population()
    for gen in range(GENERATIONS):
        population = sorted(population, key=fitness)
        new_population = []
        while len(new_population) < POP_SIZE:
            parent1, parent2 = select(population), select(population)
            child = mutate(crossover(parent1, parent2))
            new_population.append(child)
        population = new_population
        print(f"Generation {gen + 1}: Best Distance = {fitness(population[0])}")

    print("Best Route Found:", population[0])

genetic_algorithm()

18.2 Expected Output

The algorithm will evolve the population over generations, minimizing the total travel distance:

Generation 1: Best Distance = 45.23
Generation 50: Best Distance = 20.11
Generation 100: Best Distance = 15.67
Generation 200: Best Distance = 12.89
Best Route Found: [2, 6, 4, 1, 0, 8, 3, 7, 5, 9]

18.3 Why This is a Practical Application?

19. Competitive Programming & System Design Integration

19.1 Competitive Programming with Genetic Algorithms

Genetic Algorithms (GAs) are not commonly used in traditional competitive programming due to strict time limits. However, they are effective for approximation problems where brute force is infeasible.

19.1.1 Problem Types Suitable for GA
19.1.2 Example Competitive Programming Problems
19.1.3 Strategy for Competitive Programming

19.2 System Design Integration

GA is widely used in system design for optimizing parameters, configurations, and resource allocations.

19.2.1 Where GA Fits in System Design
19.2.2 Example System Design Problem

Scenario: You need to optimize cloud server allocation for an AI-based application with fluctuating workloads.

20. Assignments

20.1 Solve At Least 10 Problems Using Genetic Algorithm

Implement GA to solve the following problems:

  1. Find the maximum value of a mathematical function (e.g., f(x) = x^2 + 3x - 5).
  2. Knapsack Problem - Optimize weight vs. value selection.
  3. Travelling Salesman Problem (TSP) - Find the shortest route between cities.
  4. Graph Coloring - Minimize the number of colors needed for graph coloring.
  5. Job Scheduling - Optimize task assignments across multiple processors.
  6. Boolean Satisfiability (SAT) Problem - Find variable assignments that satisfy logic equations.
  7. Stock Portfolio Optimization - Maximize return while minimizing risk.
  8. Autonomous Vehicle Route Planning - Find the best path in a dynamic environment.
  9. Neural Network Hyperparameter Tuning - Use GA to optimize network architecture.
  10. Game AI Evolution - Use GA to evolve AI behavior in a simple game.

20.2 System Design Problem: Use GA in a Real System

Design a system where GA improves efficiency.

20.2.1 Problem Statement

Design an intelligent task scheduling system that dynamically assigns tasks to workers in a factory based on skill level and availability.

20.2.2 Requirements
20.2.3 Steps to Implement

20.3 Implement GA Under Time Constraints

Practice solving GA-based problems within strict time limits.

20.3.1 Assignment
20.3.2 Tips for Time-Constrained GA