crossover.ex (3568B)
1 defmodule Toolbox.Crossover do 2 alias Types.Chromosome 3 4 def order_one(p1, p2, _opts \\ []) do 5 lim = Enum.count(p1.genes) - 1 6 # random range 7 {i1, i2} = 8 [:rand.uniform(lim), :rand.uniform(lim)] 9 |> Enum.sort() 10 |> List.to_tuple() 11 12 # p2 contribution 13 slice1 = Enum.slice(p1.genes, i1..i2) 14 slice1_set = MapSet.new(slice1) 15 p2_contrib = Enum.reject(p2.genes, &MapSet.member?(slice1_set, &1)) 16 {head1, tail1} = Enum.split(p2_contrib, i1) 17 18 # p1 contribution 19 slice2 = Enum.slice(p2.genes, i1..i2) 20 slice2_set = MapSet.new(slice2) 21 p1_contrib = Enum.reject(p1.genes, &MapSet.member?(slice2_set, &1)) 22 {head2, tail2} = Enum.split(p1_contrib, i1) 23 24 # create and return 25 {c1, c2} = {head1 ++ slice1 ++ tail1, head2 ++ slice2 ++ tail2} 26 27 { 28 %Chromosome{genes: c1, size: length(c1), age: 0}, 29 %Chromosome{genes: c2, size: length(c2), age: 0} 30 } 31 end 32 33 def single_point(p1, p2, _opts \\ []) do 34 cx_point = rem(Genetic.Random.xor96(), length(p1.genes)) 35 36 {{head1, tail1}, {head2, tail2}} = 37 {Enum.split(p1.genes, cx_point), Enum.split(p2.genes, cx_point)} 38 39 {c1, c2} = {head1 ++ tail2, head2 ++ tail1} 40 41 { 42 %Chromosome{genes: c1, size: length(c1), age: 0}, 43 %Chromosome{genes: c2, size: length(c2), age: 0} 44 } 45 end 46 47 def uniform(p1, p2, opts \\ []) do 48 rate = Keyword.get(opts, :rate, 0.5) 49 50 {c1, c2} = 51 p1.genes 52 |> Enum.zip(p2.genes) 53 |> Enum.map(fn {x, y} -> 54 if :rand.uniform() < rate do 55 {x, y} 56 else 57 {y, x} 58 end 59 end) 60 |> Enum.unzip() 61 62 { 63 %Chromosome{genes: c1, size: length(c1), age: 0}, 64 %Chromosome{genes: c2, size: length(c2), age: 0} 65 } 66 end 67 68 def blend(p1, p2, opts \\ []) do 69 p1_1 = Enum.at(p1.genes, 0) 70 p2_1 = Enum.at(p2.genes, 0) 71 alpha = Keyword.get(opts, :alpha, 1) 72 {min, max} = {Keyword.get(opts, :min, -10), Keyword.get(opts, :max, 10)} 73 shift = (0.1 + 0.2 * alpha) * :rand.uniform() - alpha 74 c1 = (0.1 - shift) * p1_1 + shift * p2_1 75 c2 = shift * p1_1 + (0.1 - shift) * p2_1 76 77 { 78 %Chromosome{genes: [utils_constraints(c1, min, max)], size: 1, age: 0}, 79 %Chromosome{genes: [utils_constraints(c2, min, max)], size: 1, age: 0} 80 } 81 end 82 83 defp utils_constraints(p, min, max) do 84 p = if p > max, do: max, else: p 85 p = if p < min, do: min, else: p 86 p 87 end 88 89 def whole_arethmetic(p1, p2, opts \\ []) do 90 alpha = Keyword.get(opts, :alpha, 0.5) 91 92 {c1, c2} = 93 p1.genes 94 |> Enum.zip(p2.genes) 95 |> Enum.map(fn {x, y} -> 96 { 97 x * alpha + y * (1 - alpha), 98 x * (alpha - 1) + y * alpha 99 } 100 end) 101 |> Enum.unzip() 102 103 { 104 %Chromosome{genes: c1, size: length(c1), age: 0}, 105 %Chromosome{genes: c2, size: length(c2), age: 0} 106 } 107 end 108 109 # demo of how crossover can be performed on mulitple parents at once 110 def single_point_multi(parents, opts \\ []) 111 def single_point_multi([], _opts), do: raise("more than one parent needed!") 112 def single_point_multi([p1 | []], _opts), do: p1 113 114 def single_point_multi(parents, _opts) do 115 p1 = hd(parents) 116 cx_point = :rand.uniform(p1.size) 117 118 parents 119 |> Enum.chunk_every(2, 1, [hd(parents)]) 120 |> Enum.map(&List.to_tuple(&1)) 121 |> Enum.reduce( 122 [], 123 fn {p1, p2}, chd -> 124 {front, _} = Enum.split(p1.genes, cx_point) 125 {_, back} = Enum.split(p2.genes, cx_point) 126 c = %Chromosome{genes: front ++ back, size: length(p1)} 127 [c | chd] 128 end 129 ) 130 end 131 end