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:
- Routing: Assigning each operation to a machine.
- 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.

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):
- Select a random substring within two parents.
- Swap these substrings to create initial offspring. This introduces duplicates from one parent and omits genes from the other.
- A "mapping" is established between the swapped values (e.g., if 3 was swapped with 6, then 3 maps to 6).
- 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):
- Identifies "cycles" of values between the two parents. A cycle exists if the gene at position
pin Parent 1 corresponds to the gene at positionqin Parent 2, and the gene at positionqin Parent 1 corresponds to the gene at positionpin Parent 2 (this can extend over multiple positions). - 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.
- CX is useful because it preserves the absolute position of many genes from the parents.
- Identifies "cycles" of values between the two parents. A cycle exists if the gene at position
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:
- Divide the set of all jobs into two random, disjoint sets,
JobSet1andJobSet2. - To create the first offspring, copy all operations belonging to jobs in
JobSet1from Parent 1 into the offspring, preserving their positions. - 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. - 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
-
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 deduce6↔1. Let's consolidate:4↔1,7↔2,3↔3,6↔4.
- Offspring 1:
-
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, 4are already in Offspring 1. So, we need to resolve conflicts for2and1. - 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. Since7is not in Offspring 1, we can use it:[8, ?, 1, 2, 3, 4, ?, 7, ?, ?](in P1's2position, 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. But4is also a duplicate. The mapping4↔6(from6↔4) resolves this.6is not in the string. Place6:[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:
- Swap substring: O1 =
[?, ?, 1, 2, 3, 4, ?, ?, ?, ?] - Mapping:
4↔1,7↔2,6↔4. Transitive:6↔1. - 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]
- P1[0]=8. 8 is not in
- 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.
- Look at elements from P2's substring
Let's re-do, step-by-step.
-
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 -
Offspring 1 (start): Swap the middle section.
O1:? | 1 2 3 4 | ? ? ? ? ? -
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.3maps to itself. -
Fill O1 from P1: Look at the elements in P1 outside the crossover section:
8, 2, 5, 1, 9, 0. -
Element by element:
- Take
8(from P1[0]). Is8already in O1's middle part[1,2,3,4]? No. So, place8in O1 at position 0. O1:8 | 1 2 3 4 | ? ? ? ? ? - Take
2(from P1[5]). Is2in O1's middle part? Yes. We can't place it. Find its mapping:2↔7. Is7in O1's middle part? No. Place7at position 5. O1:8 | 1 2 3 4 | 7 ? ? ? ? - Take
5(from P1[6]). Is5in O1's middle part? No. Place5at position 6. O1:8 | 1 2 3 4 | 7 5 ? ? ? - Take
1(from P1[7]). Is1in O1's middle part? Yes. Find its mapping:1↔4. Is4in O1's middle part? Yes. Find its mapping:4↔6. Is6in O1's middle part? No. Place6at position 7. O1:8 | 1 2 3 4 | 7 5 6 ? ? - Take
9(from P1[8]). Is9in O1's middle part? No. Place9at position 8. O1:8 | 1 2 3 4 | 7 5 6 9 ? - Take
0(from P1[9]). Is0in O1's middle part? No. Place0at position 9. O1:8 | 1 2 3 4 | 7 5 6 9 0
- Take
-
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]. Map2↔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]. Map1↔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.
- P1[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.- O1 =
[?, 1, 2, 3, 4, ?, ?, ?, ?, ?] - Mapping
4↔1, 7↔2, 3↔3, 6↔4. - We need to fill positions 0, 5, 6, 7, 8, 9 of O1.
- Take values from P1 at those positions:
8, 2, 5, 1, 9, 0. - Check
8. Is8in[1,2,3,4]? No. O1[0] =8. - Check
2. Is2in[1,2,3,4]? Yes. Use mapping2↔7. Is7in[1,2,3,4]? No. So use7. The original position was 5. O1[5] =7. - Check
5. Is5in[1,2,3,4]? No. Original pos 6. O1[6] =5. - Check
1. Is1in[1,2,3,4]? Yes. Use mapping1↔4. Is4in[1,2,3,4]? Yes. Use mapping4↔6. Is6in[1,2,3,4]? No. Use6. Original pos 7. O1[7] =6. - Check
9. Is9in[1,2,3,4]? No. Original pos 8. O1[8] =9. - Check
0. Is0in[1,2,3,4]? No. Original pos 9. O1[9] =0.
Final O1:[8, 1, 2, 3, 4, 7, 5, 6, 9, 0].
- O1 should be
- The values from P1 are
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.