# How to Implement Genetic Algorithms for Optimization Problems Using the Genetic.ipynb Notebook

> Learn to implement genetic algorithms for optimization problems with the Genetic.ipynb notebook from Microsoft AI-For-Beginners. Explore its lightweight framework for efficient problem-solving.

- Repository: [Microsoft/AI-For-Beginners](https://github.com/microsoft/AI-For-Beginners)
- Tags: tutorial
- Published: 2026-08-27

---

**The Genetic.ipynb notebook in Microsoft's AI-For-Beginners repository provides a lightweight, self-contained genetic algorithm framework that solves optimization problems through binary vector representation, single-bit mutation, mask-based crossover, and elitist replacement.**

The microsoft/AI-For-Beginners repository offers a practical introduction to evolutionary computation in `lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb`. This notebook demonstrates how to implement genetic algorithms for optimization problems using pure Python and NumPy primitives rather than complex external libraries. The implementation includes both the core GA pipeline and working examples such as the partition problem and 8-Queens puzzle.

## Understanding the Genetic Algorithm Architecture

The Genetic.ipynb notebook follows the standard evolutionary optimization pipeline with four key components: representation, fitness evaluation, variation operators, and selection. Each component is implemented as a standalone function that you can modify for specific problem domains.

### Binary Representation and Initialization

Solutions are encoded as binary vectors matching the length of your problem data. The `generate(S)` function creates random initial populations by producing NumPy arrays of 0s and 1s:

```python
def generate(S):
    return np.array([random.randint(0,1) for _ in S])

```

In the source code at `translations/en/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb#L49-L51`, this function initializes the population `P` with `pop_size` random individuals. Each binary vector acts as a selection mask over your problem data `S`.

### Fitness Evaluation

The `fit` function computes a scalar cost measuring how far a candidate solution is from the objective. The notebook implements a partition problem fitness function that minimizes the absolute difference between two subset sums:

```python
def fit(candidate, S):
    c1 = (candidate * S).sum()
    c2 = ((1 - candidate) * S).sum()
    return abs(c1 - c2)

```

According to the implementation at `Genetic.ipynb#L82-L86`, you replace this function with any problem-specific metric while maintaining the same signature.

### Variation Operators

The notebook implements two essential genetic operators:

**Mutation** – The `mutate(b)` function flips a single randomly chosen bit to introduce genetic diversity:

```python
def mutate(b):
    x = b.copy()
    i = random.randint(0, len(b) - 1)
    x[i] = 1 - x[i]
    return x

```

**Crossover** – The `xover(b1, b2)` function creates offspring by randomly selecting bits from each parent using a generated mask:

```python
def xover(b1, b2):
    mask = generate(b1)
    return b1 * mask + b2 * (1 - mask)

```

As implemented in `Genetic.ipynb#L7-L15`, these operators provide the exploration and exploitation mechanisms necessary for effective search.

## Step-by-Step Implementation Guide

### Setting Up the Population

Initialize your optimization run by creating a diverse population of random binary vectors. The notebook demonstrates this pattern at `Genetic.ipynb#L33-L35`:

```python
import numpy as np
import random

S = np.random.randint(1, 100, size=30)  # Your problem data

pop_size = 30
P = [generate(S) for _ in range(pop_size)]

```

### Running the Evolution Loop

The `evolve(P, S, n)` function orchestrates the evolutionary process across `n` generations. It implements elitist selection by consistently replacing the worst-performing individual while preserving the best:

```python
def evolve(P, S, n=2000):
    history = []
    for _ in range(n):
        # Evaluate current generation

        fitness_scores = [fit(ind, S) for ind in P]
        best = min(fitness_scores)
        history.append(best)
        
        # Termination condition

        if best == 0:
            break
            
        # Selection and breeding (30% mutation, 70% crossover)

        worst_idx = fitness_scores.index(max(fitness_scores))
        
        if random.randint(1, 10) < 3:
            # Mutation: Replace worst with mutated random individual

            parent_idx = random.randint(0, len(P)-1)
            P[worst_idx] = mutate(P[parent_idx])
        else:
            # Crossover: Replace worst with child of two parents

            a, b = random.sample(P, 2)
            P[worst_idx] = xover(a, b)
            
    return P, history

```

The source code at `Genetic.ipynb#L70-L80` shows that the algorithm favors crossover (70% probability) over mutation (30% probability) to balance exploitation and exploration.

## Practical Examples from the Source Code

### Solving the Partition Problem

The notebook includes a concrete implementation of the partition problem where the goal is to divide a set of numbers into two subsets with equal sums:

```python

# Initialize problem data

S = np.array([10, 15, 20, 25, 30, 35, 40])  # Example multiset

P = [generate(S) for _ in range(50)]

# Run evolution

final_pop, fitness_history = evolve(P, S, n=2000)

# Extract best solution

best = min(final_pop, key=lambda ind: fit(ind, S))
print(f"Final partition difference: {fit(best, S)}")

```

This example demonstrates how the binary encoding naturally represents partition membership (1 = subset A, 0 = subset B).

### Adapting for the 8-Queens Problem

The Genetic.ipynb notebook extends the framework to solve the N-Queens combinatorial challenge. At `Genetic.ipynb#L379-L391`, the `nqueens` function evaluates board validity by checking for conflicting queen placements:

```python
def nqueens(l, N=8):
    """Evaluate fitness for N-Queens placement.
    Returns negative of conflict count (higher is better).
    """
    # Implementation evaluates diagonal conflicts

    # and uses evolve() to search for conflict-free boards

    pass

# Usage

S = np.arange(64)  # 8x8 board representation

P = [generate(S) for _ in range(30)]
solution = evolve(P, S, n=5000)

```

This adaptation shows how the same `evolve` function handles different fitness landscapes by plugging in problem-specific evaluation logic.

## Customizing the Algorithm for Your Optimization Problem

To implement genetic algorithms for your specific optimization problem using this framework:

1. **Define your data representation** – Create a vector `S` that encodes your problem dimensionality
2. **Implement a fitness function** – Replace `fit(candidate, S)` with your objective function, ensuring it returns a scalar where lower values indicate better solutions
3. **Adjust population parameters** – Modify `pop_size` and generation count `n` based on search space complexity
4. **Tune operator probabilities** – Change the 30/70 mutation/crossover split in the evolve loop if your problem requires more exploration or exploitation

The companion notebook `lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb` demonstrates this customization process for solving Diophantine equations, providing a template for integer programming applications.

## Summary

- The Genetic.ipynb notebook in `microsoft/AI-For-Beginners` provides a complete genetic algorithm implementation using binary vector representation and NumPy operations.
- Core functions include `generate()` for initialization, `fit()` for evaluation, `mutate()` and `xover()` for variation, and `evolve()` for the main selection loop.
- The algorithm uses elitist replacement with a 70/30 crossover-to-mutation ratio to evolve solutions over specified generations.
- You can adapt the framework for any optimization problem by customizing the fitness function while maintaining the binary vector structure.
- Working examples include the number partition problem and the 8-Queens puzzle, demonstrating versatility across combinatorial optimization tasks.

## Frequently Asked Questions

### What types of optimization problems work best with the Genetic.ipynb implementation?

The Genetic.ipynb implementation excels at **combinatorial optimization** problems where solutions can be encoded as binary vectors, including subset selection, partitioning, and constraint satisfaction problems like N-Queens. The framework works best when your problem has a clear binary distinction (include/exclude, select/reject) and a quantifiable fitness metric that returns a scalar cost value.

### How does the selection mechanism determine which individuals survive?

The `evolve` function implements **elitist selection** by always replacing the worst-performing individual (highest fitness value) with either a mutated copy or a crossover child. This ensures the population quality never degrades significantly, as the best solutions persist across generations while inferior candidates are gradually eliminated.

### Can I adjust the mutation rate and crossover probability?

Yes. The probabilities are hardcoded in the `evolve` function at lines 70-80 as `random.randint(1, 10) < 3` for mutation (30%) and the else branch for crossover (70%). You can modify these thresholds to increase mutation for more exploration (higher diversity) or increase crossover for more exploitation (faster convergence) depending on your problem's fitness landscape.

### Where can I find additional examples of this genetic algorithm framework?

The repository includes `Diophantine.ipynb` in the same directory (`lessons/6-Other/21-GeneticAlgorithms/`), which applies the same GA scaffold to solving Diophantine equations. This companion notebook demonstrates how to adapt the `fit` function for mathematical constraints while using the identical `evolve`, `mutate`, and `xover` primitives, providing a template for integer programming and equation solving applications.