Skip to content

Commit cc7ffe2

Browse files
committed
allow better control of RNG (part 2)
- use `rng` option in DE & ES - updates tests to use `rng` option
1 parent e6b997b commit cc7ffe2

12 files changed

+165
-126
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pkg> add Evolutionary
2626
- Genetic Programming (TreeGP)
2727

2828
## Operators
29-
a
29+
3030
- Mutations
3131
- ES
3232
- (an)isotropic Gaussian

src/de.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function update_state!(objfun, constraints, state, population::AbstractVector{IT
4848
Np = method.populationSize
4949
n = method.n
5050
F = method.F
51+
rng = options.rng
5152

5253
offspring = Array{IT}(undef, Np)
5354

@@ -61,12 +62,12 @@ function update_state!(objfun, constraints, state, population::AbstractVector{IT
6162
offspring[i] = copy(base)
6263
# println("$i => base:", offspring[i])
6364

64-
targets = randexcl(1:Np, [i], 2*n)
65+
targets = randexcl(rng, 1:Np, [i], 2*n)
6566
offspring[i] = differentiation(offspring[i], @view population[targets]; F=F)
6667
# println("$i => mutated:", offspring[i], ", targets:", targets)
6768

6869
# recombination
69-
offspring[i], _ = method.recombination(offspring[i], base)
70+
offspring[i], _ = method.recombination(offspring[i], base, rng=rng)
7071
# println("$i => recombined:", offspring[i])
7172
end
7273

src/es.jl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ The constructor takes following keyword arguments:
66
- `initStrategy`: an initial strategy description, (default: empty)
77
- `recombination`: ES recombination function for population (default: `first`), see [Crossover](@ref)
88
- `srecombination`: ES recombination function for strategies (default: `first`), see [Crossover](@ref)
9-
- `mutation`: [Mutation](@ref) function for population (default: `first`)
10-
- `smutation`: [Mutation](@ref) function for strategies (default: `identity`)
9+
- `mutation`: [Mutation](@ref) function for population (default: [`nop`](@ref))
10+
- `smutation`: [Mutation](@ref) function for strategies (default: [`nop`](@ref))
1111
- `μ`/`mu`: the number of parents
1212
- `ρ`/`rho`: the mixing number, ρ ≤ μ, (i.e., the number of parents involved in the procreation of an offspring)
1313
- `λ`/`lambda`: the number of offspring
@@ -27,8 +27,8 @@ struct ES <: AbstractOptimizer
2727
ES(; initStrategy::AbstractStrategy = NoStrategy(),
2828
recombination::Function = first,
2929
srecombination::Function = first,
30-
mutation::Function = ((r,s) -> r),
31-
smutation::Function = identity,
30+
mutation::Function = nop,
31+
smutation::Function = nop,
3232
μ::Integer = 1,
3333
mu::Integer = μ,
3434
ρ::Integer = μ,
@@ -74,6 +74,7 @@ end
7474
function update_state!(objfun, constraints, state, population::AbstractVector{IT}, method::ES, options, itr) where {IT}
7575
@unpack initStrategy,recombination,srecombination,mutation,smutation,μ,ρ,λ,selection = method
7676
evaltype = options.parallelization
77+
rng = options.rng
7778

7879
@assert ρ <= μ "Number of parents involved in the procreation of an offspring should be no more then total number of parents"
7980
if selection == :comma
@@ -86,21 +87,21 @@ function update_state!(objfun, constraints, state, population::AbstractVector{IT
8687
for i in 1:λ
8788
# Recombine the ρ selected parents to form a recombinant individual
8889
if ρ == 1
89-
j = rand(1:μ)
90+
j = rand(rng, 1:μ)
9091
recombinantStrategy = state.strategies[j]
9192
recombinant = copy(population[j])
9293
else
93-
idx = randperm(μ)[1:ρ]
94+
idx = randperm(rng, μ)[1:ρ]
9495
recombinantStrategy = srecombination(state.strategies[idx])
95-
recombinant = recombination(population[idx])
96+
recombinant = recombination(population[idx]; rng=rng)
9697
end
9798

9899
# Mutate the strategy parameter set of the recombinant
99-
stgoff[i] = smutation(recombinantStrategy)
100+
stgoff[i] = smutation(recombinantStrategy; rng=rng)
100101

101102
# Mutate the objective parameter set of the recombinant using the mutated strategy parameter set
102103
# to control the statistical properties of the object parameter mutation
103-
off = mutation(recombinant, stgoff[i])
104+
off = mutation(recombinant, stgoff[i]; rng=rng)
104105

105106
# Apply constraints
106107
offspring[i] = apply!(constraints, off)
@@ -136,3 +137,4 @@ function update_state!(objfun, constraints, state, population::AbstractVector{IT
136137

137138
return false
138139
end
140+

src/ga.jl

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,54 +58,72 @@ function initial_state(method::GA, options, objfun, population)
5858
return GAState(N, eliteSize, minfit, fitness, copy(population[fitidx]))
5959
end
6060

61-
function update_state!(objfun, constraints, state, population::AbstractVector{IT}, method::GA, options, itr) where {IT}
62-
@unpack populationSize,crossoverRate,mutationRate,ɛ,selection,crossover,mutation = method
61+
function update_state!(objfun, constraints, state, parents::AbstractVector{IT}, method::GA, options, itr) where {IT}
62+
populationSize = method.populationSize
6363
evaltype = options.parallelization
6464
rng = options.rng
65-
offspring = similar(population)
65+
offspring = similar(parents)
6666

67-
# Select offspring
68-
selected = selection(state.fitpop, populationSize, rng=rng)
67+
# select offspring
68+
selected = method.selection(state.fitpop, populationSize, rng=rng)
6969

70-
# Perform mating
71-
offidx = randperm(rng, populationSize)
70+
# perform mating
7271
offspringSize = populationSize - state.eliteSize
73-
for i in 1:2:offspringSize
74-
j = (i == offspringSize) ? i-1 : i+1
75-
if rand(rng) < crossoverRate
76-
offspring[i], offspring[j] = crossover(population[selected[offidx[i]]], population[selected[offidx[j]]], rng=rng)
77-
else
78-
offspring[i], offspring[j] = population[selected[i]], population[selected[j]]
79-
end
80-
end
72+
recombine!(offspring, parents, selected, method, offspringSize)
8173

8274
# Elitism (copy population individuals before they pass to the offspring & get mutated)
8375
fitidxs = sortperm(state.fitpop)
8476
for i in 1:state.eliteSize
8577
subs = offspringSize+i
86-
offspring[subs] = copy(population[fitidxs[i]])
78+
offspring[subs] = copy(parents[fitidxs[i]])
8779
end
8880

89-
# Perform mutation
90-
for i in 1:offspringSize
91-
if rand(rng) < mutationRate
92-
mutation(offspring[i], rng=rng)
93-
end
94-
end
81+
# perform mutation
82+
mutate!(offspring, method, constraints, rng=rng)
9583

96-
# Create new generation & evaluate it
97-
for i in 1:populationSize
98-
population[i] = apply!(constraints, offspring[i])
99-
end
10084
# calculate fitness of the population
101-
value!(objfun, state.fitpop, population)
102-
# apply penalty to fitness
103-
penalty!(state.fitpop, constraints, population)
85+
evaluate!(objfun, state.fitpop, offspring, constraints)
10486

105-
# find the best individual
87+
# select the best individual
10688
minfit, fitidx = findmin(state.fitpop)
107-
state.fittest = population[fitidx]
89+
state.fittest = parents[fitidx]
10890
state.fitness = state.fitpop[fitidx]
91+
92+
# replace population
93+
parents .= offspring
10994

11095
return false
11196
end
97+
98+
function recombine!(offspring, parents, selected, method, n=length(selected);
99+
rng::AbstractRNG=Random.GLOBAL_RNG)
100+
mates = ((i,i == n ? i-1 : i+1) for i in 1:2:n)
101+
for (i,j) in mates
102+
p1, p2 = parents[selected[i]], parents[selected[j]]
103+
if rand(rng) < method.crossoverRate
104+
offspring[i], offspring[j] = method.crossover(p1, p2, rng=rng)
105+
else
106+
offspring[i], offspring[j] = p1, p2
107+
end
108+
end
109+
110+
end
111+
112+
function mutate!(population, method, constraints;
113+
rng::AbstractRNG=Random.GLOBAL_RNG)
114+
n = length(population)
115+
for i in 1:n
116+
if rand(rng) < method.mutationRate
117+
method.mutation(population[i], rng=rng)
118+
end
119+
apply!(constraints, population[i])
120+
end
121+
end
122+
123+
function evaluate!(objfun, fitness, population, constraints)
124+
# calculate fitness of the population
125+
value!(objfun, fitness, population)
126+
# apply penalty to fitness
127+
penalty!(fitness, constraints, population)
128+
end
129+

src/mutations.jl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
# Evolutionary mutations
55
# ======================
66

7+
"""
8+
nop(s::AbstractStrategy)
9+
10+
This is a dummy mutation operator that does not change recombinant.
11+
"""
12+
nop(recombinant::AbstractVector, s::AbstractStrategy; kwargs...) = recombinant
13+
714
"""
815
gaussian(x, s::IsotropicStrategy)
916
@@ -57,6 +64,14 @@ end
5764
# Strategy mutation operators
5865
# ===========================
5966

67+
"""
68+
nop(s::AbstractStrategy)
69+
70+
This is a dummy operator that does not change strategy.
71+
"""
72+
nop(s::AbstractStrategy; kwargs...) = s
73+
74+
6075
"""
6176
gaussian(s::IsotropicStrategy)
6277
@@ -411,7 +426,7 @@ end
411426
# ======================
412427

413428
"""
414-
subtree(method::TreeGP; growth::Real = 0.1)
429+
subtree(method::TreeGP; growth::Real = 0.0)
415430
416431
Returns an in-place expression mutation function that performs mutation of an arbitrary expression subtree with a randomly generated one [^5].
417432
@@ -523,6 +538,7 @@ function swap!(v::T, from::Int, to::Int) where {T <: AbstractVector}
523538
end
524539

525540
function mutationwrapper(gamutation::Function)
526-
wrapper(recombinant::T, s::S) where {T <: AbstractVector, S <: AbstractStrategy} = gamutation(recombinant)
541+
wrapper(recombinant::T, s::S; kwargs...) where {T <: AbstractVector, S <: AbstractStrategy} = gamutation(recombinant; kwargs...)
527542
return wrapper
528543
end
544+

src/nsga2.jl

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,36 +67,22 @@ function initial_state(method::NSGA2, options, objfun, parents)
6767
end
6868

6969
function update_state!(objfun, constraints, state, parents::AbstractVector{IT}, method::NSGA2, options, itr) where {IT}
70-
@unpack populationSize,crossoverRate,mutationRate,selection,crossover,mutation = method
70+
populationSize = method.populationSize
71+
rng = options.rng
7172

72-
# Select offspring
73+
# select offspring
7374
specFit = StackView(state.ranks, state.crowding, dims=1)
74-
selected = selection(view(specFit, :, 1:populationSize), populationSize)
75-
76-
# Perform mating
77-
offidx = randperm(populationSize)
78-
for i in 1:2:populationSize
79-
j = (i == populationSize) ? i-1 : i+1
80-
if rand() < crossoverRate
81-
state.offspring[i], state.offspring[j] = crossover(parents[selected[offidx[i]]], parents[selected[offidx[j]]])
82-
else
83-
state.offspring[i], state.offspring[j] = parents[selected[i]], parents[selected[j]]
84-
end
85-
end
75+
selected = method.selection(view(specFit,:,1:populationSize), populationSize; rng=rng)
8676

87-
# Perform mutation
88-
for i in 1:populationSize
89-
if rand() < mutationRate
90-
mutation(state.offspring[i])
91-
end
92-
apply!(constraints, state.offspring[i])
93-
end
77+
# perform mating
78+
recombine!(state.offspring, parents, selected, method)
79+
80+
# perform mutation
81+
mutate!(state.offspring, method, constraints, rng=rng)
9482

9583
# calculate fitness of the offspring
9684
offfit = @view state.fitpop[:, populationSize+1:end]
97-
value!(objfun, offfit, state.offspring)
98-
# apply penalty to fitness
99-
penalty!(offfit, constraints, state.offspring)
85+
evaluate!(objfun, offfit, state.offspring, constraints)
10086

10187
# calculate ranks & crowding for population
10288
F = nondominatedsort!(state.ranks, state.fitpop)
@@ -117,6 +103,7 @@ function update_state!(objfun, constraints, state, parents::AbstractVector{IT},
117103
state.fittest = state.population[F[1]]
118104
# and keep their fitness
119105
state.fitness = state.fitpop[:,F[1]]
106+
120107
# construct new parent population
121108
parents .= state.population[fitidx]
122109

test/knapsack.jl

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
@testset "Knapsack" begin
2-
Random.seed!(42)
2+
rng = StableRNG(42)
33

44
mass = [1, 5, 3, 7, 2, 10, 5]
55
utility = [1, 3, 5, 2, 5, 8, 3]
66

77
fitnessFun = n -> (sum(mass .* n) <= 20) ? sum(utility .* n) : 0
88

9-
initpop = collect(rand(Bool,length(mass)))
10-
9+
Random.seed!(rng, 42)
10+
initpop = ()->rand(rng, Bool, length(mass))
1111
result = Evolutionary.optimize(
1212
x -> -fitnessFun(x),
1313
initpop,
1414
GA(
15-
selection = tournament(3),
16-
mutation = inversion,
15+
selection = roulette,
16+
mutation = swap2,
1717
crossover = SPX,
18-
mutationRate = 0.9,
19-
crossoverRate = 0.2,
20-
ɛ = 0.1, # Elitism
18+
mutationRate = 0.05,
19+
crossoverRate = 0.85,
20+
ɛ = 0.05, # Elitism
2121
populationSize = 100,
22-
));
23-
println("GA:RLT:INV:SP (-objfun) => F: $(minimum(result)), C: $(Evolutionary.iterations(result))")
22+
), Evolutionary.Options(show_trace=false, rng=rng)
23+
);
24+
println("GA:RLT:SWP:SPX (-objfun) => F: $(minimum(result)), C: $(Evolutionary.iterations(result))")
2425
@test abs(Evolutionary.minimum(result)) == 21.
2526
@test sum(mass .* Evolutionary.minimizer(result)) <= 20
2627

@@ -33,21 +34,22 @@
3334
uc = [20] # upper bound for constraint function
3435
con = WorstFitnessConstraints(Int[], Int[], lc, uc, cf)
3536

36-
initpop = BitVector(rand(Bool,length(mass)))
37-
37+
Random.seed!(rng, 42)
38+
initpop = BitVector(rand(rng, Bool, length(mass)))
3839
result = Evolutionary.optimize(
3940
x -> -fitnessFun(x), con,
4041
initpop,
4142
GA(
42-
selection = roulette,
43-
mutation = flip,
43+
selection = tournament(3),
44+
mutation = inversion,
4445
crossover = SPX,
45-
mutationRate = 0.9,
46-
crossoverRate = 0.1,
46+
mutationRate = 0.05,
47+
crossoverRate = 0.85,
4748
ɛ = 0.1, # Elitism
4849
populationSize = 50,
49-
));
50-
println("GA:RLT:FL:SP (-objfun) => F: $(minimum(result)), C: $(Evolutionary.iterations(result))")
50+
), Evolutionary.Options(show_trace=false, rng=rng, successive_f_tol=10));
51+
println("GA:TRN3:INV:SPX (-objfun) => F: $(minimum(result)), C: $(Evolutionary.iterations(result))")
5152
@test abs(Evolutionary.minimum(result)) == 21.
5253
@test sum(mass .* Evolutionary.minimizer(result)) <= 20
54+
5355
end

0 commit comments

Comments
 (0)