1

I am implementing Genetic Algorithm (GA). There are 43 numbers [Ambulance Locations] to choose from (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39) , I choose 3 places since I have 3 ambulances.

I can only put my ambulance in 3 locations among 1-39 locations (Restriction).

A chromosome sample: [000010000000000000100000000000100000000]. This represents that I want to put my Ambulance on the 5th, 19th, and 31 positions. The bits against positions 5th, 19th, and 31 are 1 and rest positions are 0. In other words, I am turning on 5-bit, 19-bit, and 31-bit.

Let's say

Parent1 (111000000000000000000000000000000000000) 

and

Parent2 (000000000000000000000000000000000000111)

After a cross-over, I am getting this:

('111000000000000000000000000000000000111', '000000000000000000000000000000000000000')

In the first off-spring, I have six 1's and in the second off-spring, I have Zero 1's. These generated off-springs are illegal for me since I need off-springs string with three 1's only.

I am using one-point cross-over. This is my code:

from typing import Union
import random

Parent 1 ="111000000000000000000000000000000000000"
Parent 2 ="000000000000000000000000000000000000111"


def crossover(cs1: str, cs2: str) -> Union[str, str]:
    index: int = random.randint(0, len(cs1))
    return cs1[:index] + cs2[index:], cs2[:index] + cs1[index:]

crossover(Cs1,Cs2)

What can be a good approach to perform cross-over while keeping3 bits among 1-39 bits?

  • what would expect from `000000000000000000000000000000000000111` and `000000000000000000000000000000000000111` (yes same) ? – azro Oct 06 '22 at 18:40
  • @azro Actually, none of them will work since there is no diversity in these off-springs. I need something which will help to explore my search space in a smart way. –  Oct 06 '22 at 18:59

1 Answers1

1

IIUC, you want to mix the two parents randomly, keeping exactly 3 '1's?

You can get the indices of the 1s in each parent and select them randomly:

import random

Parent1 ="111000000000000000000000000000000000000"
Parent2 ="000000000000000000000000000000000000111"

def indices(s):
    return {i for i,c in enumerate(s) if c=='1'}

def crossover(p1, p2, k=3):

    idx = set(random.sample(list(indices(p1) | indices(p2)), k=k))

    return ''.join('1' if i in idx else '0' for i in range(len(p1)))

out = crossover(Parent1, Parent2, k=Parent1.count('1'))
# '110000000000000000000000000000000000100'

If you want to give more weight to a position that is 1 in both strings, you can modify the above to use a Counter in place of a set:

import random
from collections import Counter

Parent1 ="111000000000000000000000000000000000000"
Parent2 ="000000000000000000000000000000000000111"

def indices(s):
    return Counter(i for i,c in enumerate(s) if c=='1')

def crossover(p1, p2, k=3):

    # count the number of 1 per position
    pool = indices(p1) | indices(p2)
    
    # randomly select indices
    # using the counts as weights
    idx = set(random.sample(list(pool),
                            counts=pool.values(),
                            k=k))

    return ''.join('1' if i in idx else '0' for i in range(len(p1)))

out = crossover(Parent1, Parent2, k=Parent1.count('1'))
# '010000000000000000000000000000000000101'

shuffle the bits between two offsprings:

using set operations

import random

def indices(s):
    return {i for i,c in enumerate(s) if c=='1'}

def crossover(p1, p2):
    # get positions of 1s for each string
    idx1 = indices(p1)
    idx2 = indices(p2)
    
    # positions that are different in both strings
    differ = idx1.symmetric_difference(idx2)
    # identical positions
    common = idx1&idx2
    
    # pick half of the different positions randomly
    select = set(random.sample(list(differ), k=len(differ)//2))
    
    # offspring 1 get those positions + the common ones
    select1 = select | common
    # offstring 2 gets the other positions + the common ones
    select2 = (differ-select) | common
    
    # make strings from the selected positions for each offspring
    out1 = ''.join('1' if i in select1 else '0' for i in range(len(p1)))
    out2 = ''.join('1' if i in select2 else '0' for i in range(len(p1)))
    
    return out1, out2
    
crossover(Parent1, Parent2)

example output:

('101000000000000000000000000000000000001',
 '010000000000000000000000000000000000110')
mozway
  • 194,879
  • 13
  • 39
  • 75
  • This seems very reasonable. a very smart way. However, I need two off-springs to carry on. Following this solution, how can we get two off-spring? –  Oct 06 '22 at 18:57
  • Are the two offsprings independent? exclusive in terms of bits? – mozway Oct 06 '22 at 18:58
  • Usually, when we do a cross-over from two parents, we get two off-springs. If we take these parents, `Parent1 ="111000000000000000000000000000000000000"` `Parent2 ="000000000000000000000000000000000000111"`. Following your solution, we are getting `1 off-spring` which is `'010000000000000000000000000000000000101'`. Is it possible to get `two off-springs` following the same logic or criteria? Do you get my point? –  Oct 06 '22 at 19:03
  • check the update for complementary offsprings – mozway Oct 06 '22 at 19:13
  • I think it will take months to understand this logic :D. Can you do me a favor by commenting on the code? It will help to understand in carrying the concept for my optimization problem. –  Oct 06 '22 at 19:19
  • Here are the comments ;) – mozway Oct 06 '22 at 19:24
  • Thanks a ton. I can debug and make my understanding. I am also keeping your second code which included a counter as a weight. Can you make comments on that too? Last favor :D –  Oct 06 '22 at 19:31
  • @mozway This is good stuff. I am also interested in the second code solution "give more weight to a position that is 1 in both strings, you can modify the above to use a Counter in place of a set". Can you please add comments to the code? – ExploringAI Oct 06 '22 at 19:44
  • @ExploringAI it's quite simple, you counts the positions with 1 for both strings and use those counts as weights in `random.sample`. Thus if a position has 1 in both strings you have twice as many chances to select this position. You could generalize this approach to n strings. – mozway Oct 06 '22 at 20:11
  • @mozway I got my confusion clear. I am using similar encoded data in a question but dealing with mutation. If possible, please have a look (https://stackoverflow.com/questions/73973590/controlling-mutation-in-39bit-string-as-a-candidate-solution-in-genetic-algorith). Do have any good idea to do mutation? – ExploringAI Oct 06 '22 at 20:51
  • @mozway I have updated my question to increase problem understanding. Please see https://stackoverflow.com/questions/73973590/controlling-mutation-in-39bit-string-as-a-candidate-solution-in-genetic-algorith – ExploringAI Oct 06 '22 at 21:01
  • @mozway is it `One Point Crossover, Multi Point Crossover, or Uniform Crossover`? – ExploringAI Oct 07 '22 at 14:13
  • I think it is a variation of `One Point Crossover`? @mozway –  Oct 12 '22 at 19:26
  • @Oceans I don't know the terminology. I would say it's similar to a multiple crossover – mozway Oct 12 '22 at 19:28
  • @mozway according to your solution, **shuffle the bits between two offsprings:** If we pass the value `Parent1 ="010000000000000000000000000000000000101"` `Parent2 ="110000000000000000000000000000000000100"`, the off-spring values will be exactly similar to the parents. i-e `Child-1:110000000000000000000000000000000000100` `Child-2:010000000000000000000000000000000000101`. Can you have a look? –  Oct 17 '22 at 10:06
  • This is a problem in this solution. It will happen as #mozway is mixing the two parents randomly (not a good solution). However, in a better solution, we need to do a cross-over upon the values according to the parents to generate offsprings. I think @mozway can make slight changes to get it. –  Oct 17 '22 at 10:20