What is it?
How to use it
helisa is available on Maven central through:
"com.softwaremill" %% "helisa" % "0.8.0"
Currently only Scala 2.12 is supported.
The two things that are absolutely required are:
A genotype, i.e. a representation of possible solutions to the problem,
a fitness function, that scores the solutions generated from the genotypes.
Using an example for guessing a number between 0 and a 100, you would have:
import com.softwaremill.helisa._ case class Guess(num: Int) (1) val genotype = () => genotypes.uniform(chromosomes.int(0, 100)) (2) def fitness(toGuess: Int) = (guess: Guess) => 1.0 / (guess.num - toGuess).abs (3)
The representation of a solution to the problem (the phenotype)
A producer of genotypes.
The fitness function - the closer to the target number, the higher the fitness score.
We use the code above to set up the
Evolver, which encapsulates all configuration and generates fresh population streams:
val evolver = Evolver(fitness(Number), genotype) (1) .populationSize(100) (2) .build() val stream = evolver.streamScalaStdLib() (3) val best = stream.drop(1000).head.bestPhenotype (4) println(best) // Some(Guess(42))
Evolverwith our genotype and fitness function.
Set the population size.
Streampopulation stream (see Integrating for more information)
Advance the stream and obtain the highest-scored phenotype.
You can additionally restrict the solution space by adding a phenotype validator:
val evolver = Evolver(fitness(Number), genotype) .populationSize(100) .phenotypeValidator(_.num % 2 == 0) (1) .build()
We know the number is even, so we’re restricting possible solutions to only those numbers.
As a reminder, the three main elements of evolution in genetic algorithms are:
the selection of fittest individuals (phenotypes),
the recombination of selected individuals to form new individuals in the next generation of the population,
the mutation of the new/remaining individuals.
Standard selectors are available from
helisa.selectors, you use them like this:
import com.softwaremill.helisa._ val evolver = Evolver(fitnessFunction, genotype) .offspringSelector(selectors.x) (1) .survivorsSelector(selectors.y) (2) .build()
Affect just the survivors.
Affects both the survivors and offspring.
Recombination and mutation
Recombination and mutation is handled are both generalized to operators, available in
helisa.operators . You use them as follows:
import com.softwaremill.helisa._ val evolver = Evolver(fitnessFunction, genotype) .geneticOperators(operators.crossover.x, (1) operators.mutation.y) (2) .build()
You can also add a custom operator by passing the appropriate function to the geneticOperatorsAsFunctions method.
See the doc of
EvolverBuilder for all
Evolver configuration options.
Monix is not supported directly, but can be taken advantage with using the other integrations,
Spark integration is coming up.