helisa icon indicating copy to clipboard operation
helisa copied to clipboard

Scala API for jenetics

= Helisa :apiUrl: https://softwaremill.github.io/helisa/latest/api/com/softwaremill/helisa/

image:https://maven-badges.herokuapp.com/maven-central/com.softwaremill/helisa_2.12/badge.svg["Latest 2.12 version", link="https://maven-badges.herokuapp.com/maven-central/com.softwaremill/helisa_2.12"]

== What is it?

Helisa (HEL-EE-SAH) is a Scala frontend for solving problems with genetic algorithms and genetic programming.

It's pretty much a Scala wrapper around the excellent http://jenetics.io[jenetics library^] footnote:[there is also https://github.com/softwaremill/helisa[a version with a JGAP backend]].

== How to use it

=== Obtaining

helisa is available on Maven central through:

[source,scala]

"com.softwaremill" %% "helisa" % "0.8.0"

Currently only Scala 2.12 is supported.

=== Basic usage

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:

[source,scala]

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>

<1> The representation of a solution to the problem (the phenotype) <2> A producer of genotypes. <3> 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:

[source,scala]

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))

<1> Initialize the Evolver with our genotype and fitness function. <2> Set the population size. <3> Obtain a Stream population stream (see <<Integrating>> for more information) <4> Advance the stream and obtain the highest-scored phenotype.

=== Validation

You can additionally restrict the solution space by adding a phenotype validator:

[source,scala]

val evolver = Evolver(fitness(Number), genotype) .populationSize(100) .phenotypeValidator(_.num % 2 == 0) <1> .build()

<1> We know the number is even, so we're restricting possible solutions to only those numbers.

=== Operators

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.

==== Selection

Standard selectors are available from {apiUrl}api/Selector$$standard$.html[helisa.selectors], you use them like this:

[source,scala]

import com.softwaremill.helisa._

val evolver = Evolver(fitnessFunction, genotype) .offspringSelector(selectors.x) <1> .survivorsSelector(selectors.y) <2> .build()

<1> Affect just the survivors. <2> Affects both the survivors and offspring.

You can also add a custom selector by passing the appropriate function to the {apiUrl}EvolverBuilder.html#survivorSelectorAsFunction(selector:com.softwaremill.helisa.Selector++[++G,Fitness++]++):com.softwaremill.helisa.EvolverBuilder++[++A,G,Fitness++]++[survivorSelectorAsFunction] or {apiUrl}EvolverBuilder.html#offspringSelectorAsFunction(selector:com.softwaremill.helisa.Selector++[++G,Fitness++]++):com.softwaremill.helisa.EvolverBuilder++[++A,G,Fitness++]++[offspringSelectorAsFunction] method.

==== Recombination and mutation

Recombination and mutation is handled are both generalized to operators, available in {apiUrl}api/GeneticOperator$.html[helisa.operators] . You use them as follows:

[source,scala]

import com.softwaremill.helisa._

val evolver = Evolver(fitnessFunction, genotype) .geneticOperators(operators.crossover.x, <1> operators.mutation.y) <2> .build()

<1> Recombination operators. <2> Mutation operators.

You can also add a custom operator by passing the appropriate function to the {apiUrl}EvolverBuilder.html#geneticOperatorsAsFunctions(operator1:com.softwaremill.helisa.GeneticOperator++[++G,Fitness++]++,rest:com.softwaremill.helisa.GeneticOperator++[++G,Fitness++]++*):com.softwaremill.helisa.EvolverBuilder++[++A,G,Fitness++]++[geneticOperatorsAsFunctions] method.

=== Other configuration

See the doc of {apiUrl}EvolverBuilder.html[EvolverBuilder] for all Evolver configuration options.

=== Integrating

Integrations for:

  • scala.collection.Iterator
  • scala.collection.Stream
  • Akka Stream Source
  • FS2 Stream for any Async
  • Reactive Streams Publisher

In addition:

  • Monix is not supported directly, but can be taken advantage with using the other integrations,
  • Spark integration is coming up.

=== Genetic programming

TBD