MaxHalford/eaopt

View on GitHub
crossover.go

Summary

Maintainability
A
3 hrs
Test Coverage
package eaopt

import (
    "math/rand"
    "sort"
)

// Type specific mutations for slices

// CrossUniformFloat64 crossover combines two individuals (the parents) into one
// (the offspring). Each parent's contribution to the Genome is determined by
// the value of a probability p. Each offspring receives a proportion of both of
// it's parents genomes. The new values are located in the hyper-rectangle
// defined between both parent's position in Cartesian space.
func CrossUniformFloat64(p1 []float64, p2 []float64, rng *rand.Rand) {
    for i := range p1 {
        var p = rng.Float64()
        var o1 = p*p1[i] + (1-p)*p2[i]
        var o2 = (1-p)*p1[i] + p*p2[i]
        p1[i] = o1
        p2[i] = o2
    }
}

// Generic mutations for slices

// Contains the deterministic part of the GNX method for testing purposes.
func gnx(p1, p2 Slice, indexes []int) {
    var (
        n      = p1.Len()
        o1     = p1.Copy()
        o2     = p2.Copy()
        toggle = true
    )
    // Add the first and last indexes
    indexes = append([]int{0}, indexes...)
    indexes = append(indexes, n)
    for i := 0; i < len(indexes)-1; i++ {
        if toggle {
            o1.Slice(indexes[i], indexes[i+1]).Replace(p1.Slice(indexes[i], indexes[i+1]))
            o2.Slice(indexes[i], indexes[i+1]).Replace(p2.Slice(indexes[i], indexes[i+1]))
        } else {
            o1.Slice(indexes[i], indexes[i+1]).Replace(p2.Slice(indexes[i], indexes[i+1]))
            o2.Slice(indexes[i], indexes[i+1]).Replace(p1.Slice(indexes[i], indexes[i+1]))
        }
        toggle = !toggle // Alternate for the new copying
    }
    p1.Replace(o1)
    p2.Replace(o2)
}

// CrossGNX (Generalized N-point Crossover). An identical point is chosen on
// each parent's genome and the mirroring segments are switched. n determines
// the number of crossovers (aka mirroring segments) to perform. n has to be
// equal or lower than the number of genes in each parent.
func CrossGNX(p1 Slice, p2 Slice, n uint, rng *rand.Rand) {
    var indexes = randomInts(n, 1, p1.Len(), rng)
    sort.Ints(indexes)
    gnx(p1, p2, indexes)
}

// CrossGNXInt calls CrossGNX on two int slices.
func CrossGNXInt(s1 []int, s2 []int, n uint, rng *rand.Rand) {
    CrossGNX(IntSlice(s1), IntSlice(s2), n, rng)
}

// CrossGNXFloat64 calls CrossGNX on two float64 slices.
func CrossGNXFloat64(s1 []float64, s2 []float64, n uint, rng *rand.Rand) {
    CrossGNX(Float64Slice(s1), Float64Slice(s2), n, rng)
}

// CrossGNXString calls CrossGNX on two string slices.
func CrossGNXString(s1 []string, s2 []string, n uint, rng *rand.Rand) {
    CrossGNX(StringSlice(s1), StringSlice(s2), n, rng)
}

// Contains the deterministic part of the PMX method for testing purposes.
func pmx(p1, p2 Slice, a, b int) {
    var (
        n  = p1.Len()
        o1 = p1.Copy()
        o2 = p2.Copy()
    )
    // Create lookup maps to quickly see if a gene has been visited
    var (
        p1Visited, p2Visited = make(set), make(set)
        o1Visited, o2Visited = make(set), make(set)
    )
    for i := a; i < b; i++ {
        p1Visited[p1.At(i)] = true
        p2Visited[p2.At(i)] = true
        o1Visited[i] = true
        o2Visited[i] = true
    }
    for i := a; i < b; i++ {
        // Find the element in the second parent that has not been copied in the first offspring
        if !p1Visited[p2.At(i)] {
            var j = i
            for o1Visited[j] {
                j, _ = search(o1.At(j), p2)
            }
            o1.Set(j, p2.At(i))
            o1Visited[j] = true
        }
        // Find the element in the first parent that has not been copied in the second offspring
        if !p2Visited[p1.At(i)] {
            var j = i
            for o2Visited[j] {
                j, _ = search(o2.At(j), p1)
            }
            o2.Set(j, p1.At(i))
            o2Visited[j] = true
        }
    }
    // Fill in the offspring's missing values with the opposite parent's values
    for i := 0; i < n; i++ {
        if !o1Visited[i] {
            o1.Set(i, p2.At(i))
        }
        if !o2Visited[i] {
            o2.Set(i, p1.At(i))
        }
    }
    p1.Replace(o1)
    p2.Replace(o2)
}

// CrossPMX (Partially Mapped Crossover). The offsprings are generated by
// copying one of the parents and then copying the other parent's values up to a
// randomly chosen crossover point. Each gene that is replaced is permuted with
// the gene that is copied in the first parent's genome. Two offsprings are
// generated in such a way (because there are two parents). The PMX method
// preserves gene uniqueness.
func CrossPMX(p1 Slice, p2 Slice, rng *rand.Rand) {
    var indexes = randomInts(2, 1, p1.Len(), rng)
    sort.Ints(indexes)
    pmx(p1, p2, indexes[0], indexes[1])
}

// CrossPMXInt calls CrossPMX on an int slice.
func CrossPMXInt(s1 []int, s2 []int, rng *rand.Rand) {
    CrossPMX(IntSlice(s1), IntSlice(s2), rng)
}

// CrossPMXFloat64 calls CrossPMX on a float64 slice.
func CrossPMXFloat64(s1 []float64, s2 []float64, rng *rand.Rand) {
    CrossPMX(Float64Slice(s1), Float64Slice(s2), rng)
}

// CrossPMXString calls CrossPMX on a string slice.
func CrossPMXString(s1 []string, s2 []string, rng *rand.Rand) {
    CrossPMX(StringSlice(s1), StringSlice(s2), rng)
}

// Contains the deterministic part of the OX method for testing purposes.
func ox(p1, p2 Slice, a, b int) {
    var (
        n  = p1.Len()
        o1 = p1.Copy()
        o2 = p2.Copy()
    )
    // Create lookup maps to quickly see if a gene has been copied from a parent or not
    var p1Occurences, p2Occurences = make(setInt), make(setInt)
    for i := b; i < a+n; i++ {
        var k = i % n
        p1Occurences[p1.At(k)]++
        p2Occurences[p2.At(k)]++
    }
    // Keep two indicators to know where to fill the offsprings
    var j1, j2 = b, b
    for i := b; i < b+n; i++ {
        var k = i % n
        if p1Occurences[p2.At(k)] > 0 {
            p1Occurences[p2.At(k)]--
            o1.Set(j1%n, p2.At(k))
            j1++
        }
        if p2Occurences[p1.At(k)] > 0 {
            p2Occurences[p1.At(k)]--
            o2.Set(j2%n, p1.At(k))
            j2++
        }
    }
    p1.Replace(o1)
    p2.Replace(o2)
}

// CrossOX (Ordered Crossover). Part of the first parent's genome is copied onto
// the first offspring's genome. Then the second parent's genome is iterated
// over, starting on the right of the part that was copied. Each gene of the
// second parent's genome is copied onto the next blank gene of the first
// offspring's genome if it wasn't already copied from the first parent. The OX
// method preserves gene uniqueness.
func CrossOX(p1 Slice, p2 Slice, rng *rand.Rand) {
    var indexes = randomInts(2, 1, p1.Len(), rng)
    sort.Ints(indexes)
    ox(p1, p2, indexes[0], indexes[1])
}

// CrossOXInt calls CrossOX on a int slice.
func CrossOXInt(s1 []int, s2 []int, rng *rand.Rand) {
    CrossOX(IntSlice(s1), IntSlice(s2), rng)
}

// CrossOXFloat64 calls CrossOX on a float64 slice.
func CrossOXFloat64(s1 []float64, s2 []float64, rng *rand.Rand) {
    CrossOX(Float64Slice(s1), Float64Slice(s2), rng)
}

// CrossOXString calls CrossOX on a string slice.
func CrossOXString(s1 []string, s2 []string, rng *rand.Rand) {
    CrossOX(StringSlice(s1), StringSlice(s2), rng)
}

// CrossCX (Cycle Crossover). Cycles between the parents are indentified, they
// are then copied alternatively onto the offsprings. The CX method is
// deterministic and preserves gene uniqueness.
func CrossCX(p1, p2 Slice) {
    var (
        o1     = p1.Copy()
        o2     = p2.Copy()
        cycles = getCycles(p1, p2)
        toggle = true
    )
    for i := 0; i < len(cycles); i++ {
        for _, j := range cycles[i] {
            if toggle {
                o1.Set(j, p1.At(j))
                o2.Set(j, p2.At(j))
            } else {
                o2.Set(j, p1.At(j))
                o1.Set(j, p2.At(j))
            }
        }
        toggle = !toggle
    }
    p1.Replace(o1)
    p2.Replace(o2)
}

// CrossCXInt calls CrossCX on an int slice.
func CrossCXInt(s1 []int, s2 []int) {
    CrossCX(IntSlice(s1), IntSlice(s2))
}

// CrossCXFloat64 calls CrossCX on a float64 slice.
func CrossCXFloat64(s1 []float64, s2 []float64) {
    CrossCX(Float64Slice(s1), Float64Slice(s2))
}

// CrossCXString calls CrossCX on a string slice.
func CrossCXString(s1 []string, s2 []string) {
    CrossCX(StringSlice(s1), StringSlice(s2))
}

// CrossERX (Edge Recombination Crossover).
func CrossERX(p1, p2 Slice) {
    var (
        n            = p1.Len()
        o1           = p1.Copy()
        o2           = p2.Copy()
        parents      = []Slice{p1, p2}
        offsprings   = []Slice{o1, o2}
        p1Neighbours = getNeighbours(p1)
        p2Neighbours = getNeighbours(p2)
        pNeighbours  = make(map[interface{}]set)
    )
    // Merge the neighbours of each parent whilst ignoring duplicates
    for i := range p1Neighbours {
        pNeighbours[i] = union(p1Neighbours[i], p2Neighbours[i])
    }
    // Hold two copies of the parent neighbours (one for each offspring)
    var neighbours = []map[interface{}]set{pNeighbours, nil}
    neighbours[1] = make(map[interface{}]set)
    for k, v := range pNeighbours {
        neighbours[1][k] = v
    }
    // Set the first element of each offspring to be the one of the
    // corresponding parent
    o1.Set(0, p1.At(0))
    o2.Set(0, p2.At(0))
    // Delete the neighbour from the adjacency set
    for i := range neighbours {
        delete(neighbours[i], parents[i].At(0))
        for j := range neighbours[i] {
            if neighbours[i][j][parents[i].At(0)] {
                delete(neighbours[i][j], parents[i].At(0))
            }
        }
    }
    for o := range offsprings {
        for i := 1; i < n; i++ {
            // Find the gene with the least neighbours
            var (
                j   interface{}
                min = 5 // There can't be more than 5 neighbours between 2 parents
            )
            for k, v := range neighbours[o] {
                if len(v) < min {
                    j = k
                    min = len(v)
                }
            }
            offsprings[o].Set(i, j)
            delete(neighbours[o], j)
            for k := range neighbours[o] {
                if neighbours[o][k][j] {
                    delete(neighbours[o][k], j)
                }
            }
        }
    }
    p1.Replace(o1)
    p2.Replace(o2)
}

// CrossERXInt calls CrossERX on an int slice.
func CrossERXInt(s1 []int, s2 []int) {
    CrossERX(IntSlice(s1), IntSlice(s2))
}

// CrossERXFloat64 callsCrossERX on a float64 slice.
func CrossERXFloat64(s1 []float64, s2 []float64) {
    CrossERX(Float64Slice(s1), Float64Slice(s2))
}

// CrossERXString calls CrossERX on a string slice.
func CrossERXString(s1 []string, s2 []string) {
    CrossERX(StringSlice(s1), StringSlice(s2))
}