Feedback on differential evolution algorithm

New crystaller here.

Requesting code review to make this more Crystally without classes, and faster.
Can I enable something like --fast-math? Disable index checking?

Does anyone have some nice guides on how to profile Crystal?

def calculate_score(xs : Array(Float64))
  s = 0.0
  xs.size.times { |i| s += xs[i] * xs[i] } # f(0) = 0, n params
  s
end

start_dt = Time.utc

params = 100
bounds = -10.0..10.0
generations = 1000
print_each = 100
pop_size = 100
mutate_range = 0.2..0.95
crossover_range = 0.1..1.0
crossover = 0.9
mutate = 0.4

trial = Array.new(params, 0.0)
pop = Array.new(pop_size) { Array.new(params) { rand(bounds) } }
scores = Array.new(pop_size) { |i| calculate_score(pop[i]) }

generations.times do |g|
  crossover, mutate = rand(crossover_range), rand(mutate_range)

  pop_size.times do |i|
    x0, x1, x2, xt = pop.sample, pop.sample, pop.sample, pop[i]
    params.times { |j| trial[j] = rand < crossover ? (x0[j] + (x1[j] - x2[j]) * mutate).clamp(bounds) : xt[j] }
    trial_score = calculate_score(trial)
    pop[i], scores[i] = trial.dup, trial_score if trial_score < scores[i]
  end

  if g.remainder(print_each) == 0
    mean = scores.sum / pop_size
    puts "GEN #{g}"
    puts "MEAN #{mean}"
    #best = scores.min
    #puts "BEST #{best.xs}"
  end
end

end_dt = Time.utc
puts end_dt - start_dt

Hi @peheje,
you can do something like this for the first function:

def calculate_score(xs : Array(Float64))
  xs.sum { |i| i**2}
end

This should be faster, and is clean enough to not require a new definition if you want :slight_smile:

Regarding the rest, your code looks good :+1:

1 Like

In the vein of using existing Enumerable methods:

scores = pop.map { |arr| calculate_score(arr) }

So succinct and pretty, thanks! Had a feeling it could be easier. :grinning:

1 Like

Really cool, thanks mate. :sunglasses:

1 Like