Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 40

Genetic Algorithms: Representation and Crossover

Recommended resources

Hello! Welcome to the third lesson in our module on metaheuristics.

In our last lesson, we established the fundamental trade-off between exploration (diversification) and exploitation (intensification) and saw how single-solution metaheuristics like Simulated Annealing and Tabu Search manage this balance.

Today, we shift our focus to a different class of algorithms: population-based metaheuristics. Our learning outcome is to apply the principles of Genetic Algorithms (GA), including chromosome representation and crossover operators. GAs are inspired by natural evolution and offer a powerful, parallel approach to searching for optimal solutions. You'll find that the interplay between selection, crossover, and mutation provides a fascinating new perspective on managing the exploration-exploitation balance.

1. The Core Idea of Genetic Algorithms

Genetic Algorithms belong to a broader class of evolutionary algorithms. They work by evolving a population of candidate solutions over many generations, iteratively improving them through processes that mimic natural selection, recombination, and mutation.

To get an intuitive feel for how this works, let's start with a video that explains the core concepts using the knapsack problem—a classic combinatorial optimization problem.

Genetic Algorithms Explained By Example

The video 'Genetic Algorithms Explained By Example' by Kie Codes provides an excellent high-level introduction. It explains why some problems are hard to solve and how GAs use evolutionary principles to find good approximate solutions.

Please watch from the beginning until 05:36. Pay attention to these key concepts: Population: A collection of candidate solutions. Chromosome: The encoding of a single solution (e.g., a binary string). Fitness Function: A function that evaluates how good a solution is. Selection: The process of choosing 'parents' for the next generation, favoring fitter individuals.

From the video, we can identify the main components of a GA:

  • A population of solutions (chromosomes).
  • A fitness function to evaluate each solution.
  • A selection mechanism to choose parents.
  • Genetic operators (crossover and mutation) to create offspring.
  • A replacement strategy to form the next generation.

This cycle directly relates to our previous lesson:

  • Selection is the primary driver of exploitation. By giving preference to fitter solutions, the algorithm intensifies the search in promising regions of the solution space.
  • Crossover and Mutation are the primary drivers of exploration. They introduce new genetic material and random changes, allowing the search to discover novel solutions and escape local optima.

2. Chromosome Representation: Encoding a Schedule

The first and most critical step in applying a GA is deciding how to represent a solution as a chromosome. The simple binary string used for the knapsack problem is not suitable for scheduling problems, where the order of tasks is paramount.

Permutation Representation for Simple Scheduling

For a standard Flow Shop or Job Shop problem, a common approach is a permutation-based representation. A chromosome is simply a list of job IDs, representing the sequence in which jobs are processed.

For jobs, a chromosome could be: [J3, J1, J4, J2]
This means Job 3 is processed first, then Job 1, and so on.

An Advanced Representation for Flexible Job Shop (FJSP)

Your thesis focuses on FJSP, which has two components:

  1. Routing: Assigning each operation to a machine.
  2. Sequencing: Ordering the operations on the assigned machines.

A simple permutation is not enough. The chromosome must encode both assignment and sequence information. A highly effective representation for this is the task sequencing list.

A genetic algorithm for the Flexible Job-shop Scheduling Problem

To understand a representation tailored for your thesis topic, we'll consult the paper 'A genetic algorithm for the Flexible Job-shop Scheduling Problem' by Pezzella et al. This paper presents a practical and powerful encoding scheme.

Please read Section 1 ('The FJSP') to refresh the problem definition, and then read Section 3.2 ('Coding'). Focus on how a solution is represented by a string of triples (i, j, k) and what each part of the triple signifies.

As the paper explains, a chromosome is a string whose length is the total number of operations. Each gene in the string corresponds to one operation and encodes its machine assignment. The order of genes in the string defines the overall processing sequence.

For an operation (the -th operation of job ) assigned to machine , the gene could be represented as a triple .

  • The sequence of these triples in the chromosome gives the dispatching order of operations.
  • The machine part () of each triple defines the machine assignment.

This representation is powerful because it encodes a complete solution for both the routing and sequencing subproblems of FJSP in a single string.


3. Crossover: Creating New Solutions from Good Parents

Crossover (or recombination) is the process of combining two parent chromosomes to create one or more offspring. The goal is to merge the desirable features of the parents.

Why Simple Crossover Fails for Permutations

Basic crossover operators, like those used for binary strings, are not suitable for permutation-based representations.

Operators for Genetic Algorithms
This image shows single-point, two-point, and uniform crossover. If you apply single-point crossover to two parent permutations like P1 = `[1,2,3,4,5]` and P2 = `[3,5,1,2,4]`, a crossover after the second position would yield an offspring like `[1,2,1,2,4]`. This is an invalid schedule because jobs 1 and 2 are repeated, while jobs 3 and 5 are missing.

To maintain the validity of the permutation, we must use specialized crossover operators.

Crossover Operators for Permutation-based Chromosomes

There are many such operators. We will focus on two classic examples to understand the mechanics, and then a third one that is particularly relevant for job shop problems.

#3. Order Coded Crossover Operators Genetic Algorithm Example in machine learning by Mahesh Huddar

This video by Mahesh Huddar provides clear, step-by-step visualizations of two important permutation crossover operators: Partially Mapped Crossover (PMX) and Cycle Crossover (CX).

Please watch the video from the beginning to 06:42. Partially Mapped Crossover (PMX) (00:13 - 03:03): Follow the process of swapping a substring and then using the 'mapping relationship' to resolve duplicates. Cycle Crossover (CX) (03:03 - 06:42): Focus on how cycles between the two parents are identified and used to construct the children.

Let's summarize the logic:

  • Partially Mapped Crossover (PMX):

    1. Select a random substring within two parents.
    2. Swap these substrings to create initial offspring. This introduces duplicates from one parent and omits genes from the other.
    3. A "mapping" is established between the swapped values (e.g., if 3 was swapped with 6, then 3 maps to 6).
    4. Outside the swapped section, any gene that is a duplicate of one already in the swapped section is replaced by its mapped value. This process is repeated until all duplicates are resolved. PMX is effective at preserving the relative order of genes.
  • Cycle Crossover (CX):

    1. Identifies "cycles" of values between the two parents. A cycle exists if the gene at position p in Parent 1 corresponds to the gene at position q in Parent 2, and the gene at position q in Parent 1 corresponds to the gene at position p in Parent 2 (this can extend over multiple positions).
    2. To create an offspring, genes from one cycle are copied from Parent 1, and genes from the next cycle are copied from Parent 2, and so on.
    3. CX is useful because it preserves the absolute position of many genes from the parents.

Precedence Preserving Crossover (POX) for Job Shop

For job shop and flexible job shop problems, we have an additional constraint: the operations within a single job must be performed in a specific order (e.g., must precede ). The chromosome representation from the Pezzella paper handles this implicitly by its decoding, but the crossover operator can also be designed to respect this.

The Pezzella paper you read earlier (Section 3.5, "Offspring generation") mentions the Precedence Preserving Order-based Crossover (POX). Here’s how it works:

  1. Divide the set of all jobs into two random, disjoint sets, JobSet1 and JobSet2.
  2. To create the first offspring, copy all operations belonging to jobs in JobSet1 from Parent 1 into the offspring, preserving their positions.
  3. Fill the remaining empty positions in the offspring with the operations from jobs in JobSet2, taking them in the order they appear in Parent 2.
  4. The second offspring is created symmetrically.

This operator is powerful because it transfers entire, valid job sub-sequences from parents to children, which can be a very effective way to propagate good scheduling patterns.

Test your understanding!

Let's apply Partially Mapped Crossover (PMX). Given the following two parents and crossover points between the second and fifth positions (inclusive):

  • Parent 1: [8, 4, 7, 3, 6, 2, 5, 1, 9, 0]
  • Parent 2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The substring from Parent 1 is [4, 7, 3, 6] and from Parent 2 is [1, 2, 3, 4]. Create Offspring 1.

Show answer
  1. Initial Swap: Copy the substring from Parent 2 into Parent 1's structure. The rest of the positions are undefined for now.

    • Offspring 1: [?, ?, 1, 2, 3, 4, ?, ?, ?, ?]
    • The mapping from the swapped P1 substring to the new P2 substring is: 4↔1, 7↔2, 3↔3, 6↔4. Since 4 also maps to 1, we can deduce 6↔1. Let's consolidate: 4↔1, 7↔2, 3↔3, 6↔4.
  2. Fill Remaining Positions: Copy the non-conflicting values from Parent 1.

    • The values from P1 are 8, 2, 5, 1, 9, 0.
    • The values 1, 2, 3, 4 are already in Offspring 1. So, we need to resolve conflicts for 2 and 1.
    • Start with 8. It's not in the substring. Place it: [8, ?, 1, 2, 3, 4, ?, ?, ?, ?]
    • Next is P1's 2. This is a duplicate. From the mapping, 2↔7. Since 7 is not in Offspring 1, we can use it: [8, ?, 1, 2, 3, 4, ?, 7, ?, ?] (in P1's 2 position, which is position 6)
    • Next is 5. Not in the substring. Place it: [8, ?, 1, 2, 3, 4, 5, 7, ?, ?]
    • Next is P1's 1. This is a duplicate. From the mapping, 1↔4. But 4 is also a duplicate. The mapping 4↔6 (from 6↔4) resolves this. 6 is not in the string. Place 6: [8, ?, 1, 2, 3, 4, 5, 7, 6, ?]
    • Next is 9. Not in the substring. Place it: [8, ?, 1, 2, 3, 4, 5, 7, 6, 9]
    • Finally, 0. Not in the substring. Place it. We have one spot left.
    • Final Offspring 1: [8, 0, 1, 2, 3, 4, 5, 7, 6, 9] (The order of filling the outer elements respects their original order in P1). A more systematic way is to check each position of P1. For P1[0]=8, it's not in the substring [1,2,3,4], so O1[0]=8. For P1[1]=4, it is in the substring. Mapping is 4->1. But 1 is also in the substring. Mapping is 1->4. Let's trace it.

    A simpler procedure:

    1. Swap substring: O1 = [?, ?, 1, 2, 3, 4, ?, ?, ?, ?]
    2. Mapping: 4↔1, 7↔2, 6↔4. Transitive: 6↔1.
    3. Fill from P1:
      • P1[0]=8. 8 is not in [1,2,3,4]. O1[0]=8. [8, ?, 1, 2, 3, 4, ?, ?, ?, ?]
      • P1[1]=4. In substring region. Skip.
      • P1[5]=2. In substring region. Skip.
      • P1[6]=5. 5 is not in [1,2,3,4]. O1[6]=5. [8, ?, 1, 2, 3, 4, 5, ?, ?, ?]
      • P1[7]=1. In substring region. Skip.
      • P1[8]=9. 9 is not in [1,2,3,4]. O1[8]=9. [8, ?, 1, 2, 3, 4, 5, ?, 9, ?]
      • P1[9]=0. 0 is not in [1,2,3,4]. O1[9]=0. [8, ?, 1, 2, 3, 4, 5, ?, 9, 0]
    4. Fill remaining from P2, using mapping on conflicts.
      • Look at elements from P2's substring [1,2,3,4] and P1's corresponding [4,7,3,6].
      • Consider element 0 from P2 (at position 0). It needs a home in O1. The element in P1 at that position is 8. Is 8 in O1's substring? No. So 0 goes where 8 was: [0, ?, 1, 2, 3, 4, 5, ?, 9, ?]. Wait, this is getting complex. Let's follow the video's logic more simply.

    Let's re-do, step-by-step.

    1. Parents & Crossover section:
      P1: 8 | 4 7 3 6 | 2 5 1 9 0
      P2: 0 | 1 2 3 4 | 5 6 7 8 9

    2. Offspring 1 (start): Swap the middle section.
      O1: ? | 1 2 3 4 | ? ? ? ? ?

    3. Mapping: From the swapped sections: 4↔1, 7↔2, 3↔3, 6↔4. This gives the mappings: {4:1, 1:4, 7:2, 2:7, 6:4, 4:6}. Combining these: (4, 1, 6) and (7, 2) are the relations. 3 maps to itself.

    4. Fill O1 from P1: Look at the elements in P1 outside the crossover section: 8, 2, 5, 1, 9, 0.

    5. Element by element:

      • Take 8 (from P1[0]). Is 8 already in O1's middle part [1,2,3,4]? No. So, place 8 in O1 at position 0. O1: 8 | 1 2 3 4 | ? ? ? ? ?
      • Take 2 (from P1[5]). Is 2 in O1's middle part? Yes. We can't place it. Find its mapping: 2↔7. Is 7 in O1's middle part? No. Place 7 at position 5. O1: 8 | 1 2 3 4 | 7 ? ? ? ?
      • Take 5 (from P1[6]). Is 5 in O1's middle part? No. Place 5 at position 6. O1: 8 | 1 2 3 4 | 7 5 ? ? ?
      • Take 1 (from P1[7]). Is 1 in O1's middle part? Yes. Find its mapping: 1↔4. Is 4 in O1's middle part? Yes. Find its mapping: 4↔6. Is 6 in O1's middle part? No. Place 6 at position 7. O1: 8 | 1 2 3 4 | 7 5 6 ? ?
      • Take 9 (from P1[8]). Is 9 in O1's middle part? No. Place 9 at position 8. O1: 8 | 1 2 3 4 | 7 5 6 9 ?
      • Take 0 (from P1[9]). Is 0 in O1's middle part? No. Place 0 at position 9. O1: 8 | 1 2 3 4 | 7 5 6 9 0
    6. Final Offspring 1: [8, 0, 1, 2, 3, 4, 7, 5, 6, 9] (Wait, I filled the outer positions in the wrong order. Let's fix).

      • O1 should be [8, ?, ?, ?, ?, ?, ?, ?, ?, ?] initially. Then copy the middle part [1,2,3,4] into positions 1-4.
      • Correct filling order:
        • P1[0]=8. Not in [1,2,3,4]. O1[0] = 8.
        • P1[5]=2. In [1,2,3,4]. Map 2↔7. 7 not in [1,2,3,4]. O1[5]=7.
        • P1[6]=5. Not in [1,2,3,4]. O1[6]=5.
        • P1[7]=1. In [1,2,3,4]. Map 1↔4↔6. 6 not in [1,2,3,4]. O1[7]=6.
        • P1[8]=9. Not in [1,2,3,4]. O1[8]=9.
        • P1[9]=0. Not in [1,2,3,4]. O1[9]=0.
      • Fill empty slot (position 1) from P2. The corresponding element in P1 is 4. The element to be copied from P2 is 1, which is in the substring. Wait, the logic is simpler. Copy P2's substring. O1 = [?, ?, 2, 3, 4, 5, ?, ?, ?, ?] using P2's substring [2,3,4,5]. The original problem had a different substring. Let me stick to the problem I wrote.
      • Original problem: P1 [4,7,3,6], P2 [1,2,3,4].
      • O1 starts as [?, ?, 1, 2, 3, 4, ?, ?, ?, ?].
      • P1 outside section: 8, ?, ?, ?, ?, 2, 5, 1, 9, 0.
      • Positions to fill in O1: 0, 1, 5, 6, 7, 8, 9.
      • Look at P2[0]=0. Value in P1 is 8. Is 8 in the substring [1,2,3,4]? No. Put 0 in O1 at position where 8 was in P1. That is pos 0. O1=[0, ?, 1, 2, 3, 4, ?, ?, ?, ?].
      • Look at P2[1]=1. Already in substring. Skip.
        ... this is not the algorithm from the video. The video's method is clearer.

      Let's re-try with the video's logic:
      P1: [8, 4, 7, 3, 6, 2, 5, 1, 9, 0]
      P2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
      Section to swap: P1's [4, 7, 3, 6] and P2's [1, 2, 3, 4].
      O1 starts as [8, ?, ?, ?, ?, 2, 5, 1, 9, 0] with empty slots in the middle.
      Then, copy P2's section: O1 becomes [8, 1, 2, 3, 4, 2, 5, 1, 9, 0]. This has duplicates.
      Let's stick to the Huddar video's method exactly.

      1. O1 = [?, 1, 2, 3, 4, ?, ?, ?, ?, ?]
      2. Mapping 4↔1, 7↔2, 3↔3, 6↔4.
      3. We need to fill positions 0, 5, 6, 7, 8, 9 of O1.
      4. Take values from P1 at those positions: 8, 2, 5, 1, 9, 0.
      5. Check 8. Is 8 in [1,2,3,4]? No. O1[0] = 8.
      6. Check 2. Is 2 in [1,2,3,4]? Yes. Use mapping 2↔7. Is 7 in [1,2,3,4]? No. So use 7. The original position was 5. O1[5] = 7.
      7. Check 5. Is 5 in [1,2,3,4]? No. Original pos 6. O1[6] = 5.
      8. Check 1. Is 1 in [1,2,3,4]? Yes. Use mapping 1↔4. Is 4 in [1,2,3,4]? Yes. Use mapping 4↔6. Is 6 in [1,2,3,4]? No. Use 6. Original pos 7. O1[7] = 6.
      9. Check 9. Is 9 in [1,2,3,4]? No. Original pos 8. O1[8] = 9.
      10. Check 0. Is 0 in [1,2,3,4]? No. Original pos 9. O1[9] = 0.
        Final O1: [8, 1, 2, 3, 4, 7, 5, 6, 9, 0].

Conclusion

Today we've laid the groundwork for using Genetic Algorithms to solve complex scheduling problems. We've moved from the single-solution view of local search to the parallel, population-based approach of evolutionary computation.

Key Takeaways:

  • Genetic Algorithms evolve a population of solutions, balancing exploitation (through selection of the fittest) and exploration (through crossover and mutation).
  • Chromosome representation is a critical design choice. Simple permutations work for basic sequencing, but complex problems like FJSP require richer encodings that capture both assignment (routing) and sequence information.
  • Crossover is the main engine for combining features of successful parent solutions. Standard operators are invalid for permutations.
  • Specialized operators like PMX and CX are required to maintain valid permutation-based chromosomes, while operators like POX are specifically designed to respect precedence constraints found in job shop environments.

Preview of the Next Lesson:

While we used Simulated Annealing (SA) as an example in our lesson on the exploration-exploitation trade-off, we have yet to do a deep dive into its mechanics. In the next lesson, we will focus exclusively on SA, covering its core components in detail. You will learn how to formally apply its acceptance criteria and design effective cooling schedules, which are the key parameters for controlling its performance.