I think I’m going to stop with my posts of my “study notes”. If anyone cares to see them as a sort of terrible Cliff’s notes for the book the file is here.
My thoughts on chapter 4:
- Constraint problems – TIL the term for that type of problem, really the entire motivation for me to pick up this book was frustration at solving the Einstein’s house problem on Exercism
I would have thought that creating a module attribute with a map or ETS table for looking up profits and weights would be the obvious solution, but I always feel clever when I make use of zip
functions. In this case you end up zipping over the collection every time you call the fitness function, so caching the weights and profits for later access is probably a better approach.
Stylistic bike-shedding alert:
zip -> map -> sum
can be rewritten as zip_reduce
If all cargo can fit the problem is trivial, so using the total available profit for ALL cargo as a termination criteria seems odd … which was apparently the author’s point LOL.
Penalty Functions
I’d like to see the penalty function defined as a private function independent of the fitness function, even if it’s only ever called by the fitness function. This would make it easier to generalize the fitness function and even include a flag in opts
to apply a penalty or not.
Termination Criteria
The goal is to produce the best solution possible, even when you don’t know that it’s the absolute best.
This concept throws me off. I’m used to thinking a fundamental concept of pure functional programming that states any function passed a given input always returns the same output. It seems that you could give a genetic algo some input and end up with slightly different output. I suppose any function interfacing with :rand
must be impure.
Possible errors in OneMax example for average fitness threshold. Since population is a list of Chromosome structs, you need to first map the population to an enumerable of just genes before calculating average fitness.
avg = population |> Enum.map(&Map.get(&1, :genes)) |> Enum.map(&(Enum.sum(&1) / length(&1)))
I might be wrong, but it seem to me that all three termination criteria converge on 42 and never terminate b/c the fitness function is just the sum and the Genetic module sorts by >=
after applying the fitness function. To achieve convergence to minimum the fitness function needs to be changed to Enum.sum(choromosome.genes) * -1
. The average is trickier. Perhaps Genetic.evaluate/3
should be passed a sorter function in addition to the fitness function. This could be included in opts
keyword list.
Tracking time since last improvement → light bulb moment for me.
Schema and theorems and heuristics, oh my! Getting into the fancy words now.
With multiple factor optimization applying weights to various factors makes me think these weights should be passed to the fitness function in opts
. Maybe require something like fitness_factors
with guards to catch that all keys in opts.fitness_factors
are fields in chromosome
.
Perceptual data – ranking cannot be calculated mathematically but ranking by user can transform into mathematical fitness via interactive fitness functions that require user input. Coming from a background of constantly interfacing with people to acquire, weight, and sort various types of information for deductive processing, this type of data is much more interesting to me.
@code-shoily – regarding genotype vs phenotype, think of it as the encoded data vs the data itself. For the ship cargo example, the phenotype refers to the collection of crates defined by their weight and profit, but the genotype is just the list of ones and zeroes that encode which crates are selected.