All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mgo.evolution.algorithm.package.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 Guillaume Chérel, Romain Reuillon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package mgo.evolution.algorithm

import mgo.evolution.*
import breeding.*
import elitism.*
import mgo.tools
import mgo.tools.*
import cats.*
import cats.implicits.*
import cats.data.*
import monocle.*
import monocle.syntax.all.*

import java.nio.ByteBuffer
import scala.reflect.ClassTag
import scala.util.Random


object Algorithm:
  import scala.concurrent.ExecutionContext
  import scala.util.Random

  lazy val parallel = Parallel(ExecutionContext.global, s => util.Random(s))

  sealed trait ParallelContext
  case class Parallel(executionContext: ExecutionContext, seeder: Long => Random) extends ParallelContext

  object Sequential extends ParallelContext

trait Algorithm[T, I, G, S]:
  def initialState(t: T, rng: Random): S
  def initialPopulation(t: T, rng: Random, parallel: Algorithm.ParallelContext): Vector[I]
  def step(t: T): (S, Vector[I], Random, Algorithm.ParallelContext) => (S, Vector[I])


type HitMapState = Map[Vector[Int], Int]
type Archive[I] = IArray[I]
object Archive:
  def empty[I: ClassTag]: Archive[I] = IArray.empty[I]

case class EvolutionState[S](
  generation: Long = 0L,
  evaluated: Long = 0L,
  startTime: Long = java.lang.System.currentTimeMillis(),
  s: S)

//  def state[M[_]: cats.Monad: StartTime: Random: Generation, T](t: T) =
//    for {
//      s <- implicitly[StartTime[M]].get
//      rng <- implicitly[Random[M]].use(identity)
//      g <- implicitly[Generation[M]].get
//    } yield EvolutionState[T](g, s, rng, t)

def randomTake[G](gs: Vector[G], lambda: Int, random: scala.util.Random): Vector[G] = random.shuffle(gs).take(lambda)

def operatorProportions[I](operation: I => Option[Int], is: Vector[I]): Map[Int, Double] =
  is.map { operation }.
    collect { case Some(op) => op }.
    groupBy(identity).
    view.mapValues(_.length.toDouble / is.size).
    toMap

def selectOperator[S, G](
  operators: IArray[(S, G, scala.util.Random) => G],
  opStats: Map[Int, Double],
  exploration: Double): (S, G, Random) => (G, Int) =

  def allOps =
    operators.zipWithIndex.map: (op, index) =>
      (op, opStats.getOrElse(index, 0.0))

  (s: S, g: G, rng: scala.util.Random) =>
    val (op, i) = drawOperator(allOps, exploration, rng)
    (op(s, g, rng), i)


def drawOperator[O](opsAndWeights: IArray[(O, Double)], exploration: Double, rng: scala.util.Random): (O, Int) =
  val explore = rng.nextDouble

  val i =
    if explore < exploration
    then rng.nextInt(opsAndWeights.size)
    else multinomialDraw(opsAndWeights.zipWithIndex.map { case ((op, w), i) => (w, i) }, rng)._1

  (opsAndWeights(i)._1, i)

object GenomeVectorDouble {

  def randomGenomes[G](cons: (IArray[Double], IArray[Int]) => G)(mu: Int, continuous: Vector[C], discrete: Vector[D], reject: Option[G => Boolean], rng: scala.util.Random): Vector[G] =
    def randomUnscaledContinuousValues(genomeLength: Int, rng: scala.util.Random) = IArray.fill(genomeLength)(() => rng.nextDouble()).map(_())
    def randomDiscreteValues(genome: Vector[D], rng: scala.util.Random): IArray[Int] =
      def part(d: D) = tools.randomInt(rng, d)
      IArray.from(genome.map(part))

    def randomG(rng: scala.util.Random) = cons(randomUnscaledContinuousValues(continuous.size, rng), randomDiscreteValues(discrete, rng))

    val rejectValue = reject.getOrElse((_: G) => false)

    def generate(acc: List[G], n: Int): Vector[G] =
      if n >= mu
      then acc.toVector
      else
        val g = randomG(rng)
        if (rejectValue(g)) generate(acc, n)
        else generate(g :: acc, n + 1)

    generate(List(), 0)

  def filterNaN[I, T](values: Vector[I], value: I => T)(implicit cbn: CanBeNaN[T]): Vector[I] =
    values.filter { i => !cbn.isNaN(value(i)) }

  def continuousCrossovers[S]: IArray[GACrossover[S, Double]] =
    IArray(
      sbxC(1.5),
      sbxC(2.0),
      sbxC(5.0),
      sbxC(20.0),
      sbxC(30.0)
    )

  def discreteCrossovers[S]: IArray[GACrossover[S, Int]] =
    IArray(
      binaryCrossover[S, Int](0.5 / _),
      binaryCrossover[S, Int](1.0 / _),
      binaryCrossover[S, Int](2.0 / _),
      binaryCrossover[S, Int](_ => 0.1),
      binaryCrossover[S, Int](_ => 0.5)
    )

  def continuousMutations[S]: IArray[GAMutation[S]] =
    val locals =
      for
        exp <- 1 to 7
      yield
        gaussianMutation(mutationRate = 1.0 / _, sigma = Math.pow(10, -exp))

    IArray.unsafeFromArray(locals.toArray) ++
      Vector(
        gaussianMutation(mutationRate = _ => 0.1, sigma = 0.1),
        gaussianMutation(mutationRate = _ => 0.5, sigma = 0.5),
        gaussianMutation(mutationRate = _ => 0.8, sigma = 0.5)
      )

  def discreteMutations[S](discrete: Vector[D]): IArray[Mutation[S, IArray[Int], IArray[Int]]] =
    IArray(
      randomMutation(0.5 / _, discrete),
      randomMutation(1.0 / _, discrete),
      randomMutation(2.0 / _, discrete),
      randomMutation(_ => 0.1, discrete),
      randomMutation(_ => 0.5, discrete),
      randomMutation(_ => 0.8, discrete)
    )

  type CrossoverAndMutation[S, G] = (S, G, scala.util.Random) => G

  def continuousCrossoversAndMutations[S]: IArray[CrossoverAndMutation[S, (IArray[Double], IArray[Double])]] =
    for
      c <- continuousCrossovers[S]
      m <- continuousMutations[S]
    yield crossoverAndMutation[S, IArray[Double]](c, m)

  def discreteCrossoversAndMutations[S](discrete: Vector[D]): IArray[CrossoverAndMutation[S, (IArray[Int], IArray[Int])]] =
    for
      c <- discreteCrossovers[S]
      m <- discreteMutations[S](discrete)
    yield crossoverAndMutation[S, IArray[Int]](c, m)

  def crossoverAndMutation[S, G](crossover: Crossover[S, (G, G), (G, G)], mutation: Mutation[S, G, G]): CrossoverAndMutation[S, (G, G)] =
    (s, mates, rng) =>
      val crossed = crossover(s, mates, rng)
      val m1 = mutation(s, crossed._1, rng)
      val m2 = mutation(s, crossed._2, rng)
      (m1, m2)

  def applyOperators[S, I, G](
    crossover: Crossover[S, (G, G), (G, G)],
    mutation: Mutation[S, G, G],
    selection: Selection[S, I],
    genome: I => G)(s: S, population: Vector[I], rng: scala.util.Random): (G, G) =
    val m1 = selection(s, population, rng)
    val m2 = selection(s, population, rng)
    crossoverAndMutation[S, G](crossover, mutation) apply (s, (genome(m1), genome(m2)), rng)

  def applyContinuousDynamicOperators[S, I](
    selection: Selection[S, I],
    genome: I => IArray[Double],
    operatorStatistics: Map[Int, Double],
    operatorExploration: Double): (S, Vector[I], Random) => ((IArray[Double], IArray[Double]), Int) =

    def applyOperator =
      selectOperator(
        continuousCrossoversAndMutations[S],
        operatorStatistics,
        operatorExploration)

    (s: S, population: Vector[I], rng: scala.util.Random) =>
      val m1 = selection(s, population, rng)
      val m2 = selection(s, population, rng)
      applyOperator(s, (genome(m1), genome(m2)), rng)


  def applyDynamicOperators[S, I, G](
    selection: Selection[S, I],
    genomeValues: I => (IArray[Double], IArray[Int]),
    continuousOperatorStatistics: Map[Int, Double],
    discreteOperatorStatistics: Map[Int, Double],
    discrete: Vector[D],
    operatorExploration: Double,
    buildGenome: (IArray[Double], Option[Int], IArray[Int], Option[Int]) => G): (S, Vector[I], Random) => Vector[G] =

    def continuousOperator =
      selectOperator(
        continuousCrossoversAndMutations[S],
        continuousOperatorStatistics,
        operatorExploration)

    def discreteOperator =
      selectOperator(
        discreteCrossoversAndMutations[S](discrete),
        discreteOperatorStatistics,
        operatorExploration)

    (s: S, population: Vector[I], rng: scala.util.Random) =>
      val m1 = selection(s, population, rng)
      val m2 = selection(s, population, rng)
      val (c1, d1) = genomeValues(m1)
      val (c2, d2) = genomeValues(m2)

      val cOff = continuousOperator(s, (c1, c2), rng)
      val dOff = discreteOperator(s, (d1, d2), rng)

      val ((cOff1, cOff2), cop) = cOff
      val ((dOff1, dOff2), dop) = dOff

      import mgo.tools.clamp

      val ng1 = buildGenome(cOff1.map(clamp(_)), Some(cop), dOff1, Some(dop))
      val ng2 = buildGenome(cOff2.map(clamp(_)), Some(cop), dOff2, Some(dop))

      Vector(ng1, ng2)
}

object Aggregation:
  def average(history: Vector[Vector[Double]]): Vector[Double] = history.transpose.map(o => o.sum / o.size)
  def median(history: Vector[Vector[Double]]): Vector[Double] = history.transpose.map(tools.median)

def scaleContinuousValues(values: IArray[Double], genomeComponents: Vector[C]): IArray[Double] =
  val size = values.size
  val res = Array.ofDim[Double](size)

  loop(
    0,
    _ < size,
    i => i + 1
  ): i =>
    res(i) = values(i).scale(genomeComponents(i))

  IArray.unsafeFromArray(res)

def scaleContinuousVectorValues(values: Vector[Double], genomeComponents: Vector[C]): Vector[Double] =
  scaleContinuousValues(IArray.from(values), genomeComponents).toVector



object CDGenome:

  object DeterministicIndividual:
    case class Individual[P](genome: Genome, phenotype: P, generation: Long, initial: Boolean)

    def individualFitness[P](fitness: P => Vector[Double]): Individual[P] => Vector[Double] = Focus[DeterministicIndividual.Individual[P]](_.phenotype).get _ andThen fitness
    def buildIndividual[P](g: Genome, p: P, generation: Long, initial: Boolean): Individual[P] = Individual(g, p, generation, initial)

    def expression[P](express: (IArray[Double], IArray[Int]) => P, components: Vector[C], discrete: Vector[D]): (Genome, Long, Boolean) => Individual[P] =
      deterministic.expression[Genome, P, Individual[P]](
        scaledValues(components, discrete),
        buildIndividual[P],
        express)

  object NoisyIndividual:

    def aggregate[P: Manifest](i: Individual[P], aggregation: Vector[P] => Vector[Double], continuous: Vector[C], discrete: Vector[D]): (Vector[Double], Vector[Int], Vector[Double], Int) =
      (
        scaleContinuousVectorValues(continuousVectorValues(continuous).get(i.genome), continuous),
        i.focus(_.genome) andThen discreteVectorValues(discrete) get,
        aggregation(vectorPhenotype[P].get(i)),
        i.focus(_.phenotypeHistory).get.size
      )

    case class Individual[P](genome: Genome, phenotypeHistory: IArray[P], historyAge: Long, generation: Long, initial: Boolean)

    def buildIndividual[P: Manifest](g: Genome, f: P, generation: Long, initial: Boolean): Individual[P] = Individual[P](g, IArray(f), 1, generation, initial)
    def vectorPhenotype[P: Manifest]: PLens[Individual[P], Individual[P], Vector[P], Vector[P]] = Focus[Individual[P]](_.phenotypeHistory) andThen arrayToVectorIso[P]

    def expression[P: Manifest](fitness: (util.Random, IArray[Double], IArray[Int]) => P, continuous: Vector[C], discrete: Vector[D]): (util.Random, Genome, Long, Boolean) => Individual[P] =
      noisy.expression[Genome, Individual[P], P](
        scaledValues(continuous, discrete),
        buildIndividual[P])(fitness)

  object Genome:
    extension (g: Genome)
      def discreteValues(d: Vector[D]) = CDGenome.discreteValues(d).get(g)
      def continuousValues(c: Vector[C]) = CDGenome.continuousValues(c).get(g)
      def scaledContinuousValues(c: Vector[C]) = scaleContinuousValues(CDGenome.continuousValues(c).get(g), c)

  case class Genome(
     continuousValues: IArray[Double],
     continuousOperator: Byte,
     compactedDiscreteValues: Compacted.CompactedArrayInt,
     discreteOperator: Byte)

  def buildGenome(d: Vector[D])(
    continuous: IArray[Double],
    continuousOperator: Option[Int],
    discrete: IArray[Int],
    discreteOperator: Option[Int]): Genome =
    Genome(
      continuous,
      continuousOperator.getOrElse(-1).toByte,
      Compacted.CompactedArrayInt.compact(discrete, d),
      discreteOperator.getOrElse(-1).toByte)


  def continuousValues(c: Vector[C]) = Focus[Genome](_.continuousValues)
  def continuousVectorValues(c: Vector[C]): PLens[Genome, Genome, Vector[Double], Vector[Double]] = Focus[Genome](_.continuousValues) andThen arrayToVectorIso[Double]
  def continuousOperator: PLens[Genome, Genome, Option[Int], Option[Int]] = Focus[Genome](_.continuousOperator) andThen byteToUnsignedIntOption

  def discreteValues(d: Vector[D]) = Focus[Genome](_.compactedDiscreteValues) andThen Compacted.CompactedArrayInt.iso(d)
  def discreteVectorValues(d: Vector[D]): PLens[Genome, Genome, Vector[Int], Vector[Int]] = discreteValues(d) andThen arrayToVectorIso[Int]
  def discreteOperator: PLens[Genome, Genome, Option[Int], Option[Int]] = Focus[Genome](_.discreteOperator) andThen byteToUnsignedIntOption


  def scaledValues(continuous: Vector[C], d: Vector[D]) = (g: Genome) =>
    (scaleContinuousValues(continuousValues(continuous).get(g), continuous), discreteValues(d).get(g))

  def scaledVectorValues(continuous: Vector[C], d: Vector[D]) = (g: Genome) =>
    (scaleContinuousVectorValues(continuousVectorValues(continuous).get(g), continuous), discreteVectorValues(d).get(g))

  def initialGenomes(lambda: Int, continuous: Vector[C], discrete: Vector[D], reject: Option[Genome => Boolean], rng: scala.util.Random): Vector[Genome] =
    GenomeVectorDouble.randomGenomes[Genome]((c, d) => buildGenome(discrete)(c, None, d, None))(lambda, continuous, discrete, reject, rng)


  object Compacted:
    object CompactedArrayInt:
      def iso(d: Vector[D]) =
        Iso[CompactedArrayInt, IArray[Int]](c => decompact(c, d))(a => compact(a, d))

      def compact(a: IArray[Int], d: Vector[D]): CompactedArrayInt =
        val size = a.length
        val buffer = ByteBuffer.allocate(d.map(_.precision.size).sum)

        loop(0, _ < size, _ + 1): i =>
          val dValue = d(i)

          def toByte(v: Int, d: D) = (v - d.low + Byte.MinValue).toByte
          def toShort(v: Int, d: D) = (v - d.low + Short.MinValue).toShort

          dValue.precision match
            case D.IntegerPrecision.Byte => buffer.put(toByte(a(i), dValue))
            case D.IntegerPrecision.Short => buffer.putShort(toShort(a(i), dValue))
            case D.IntegerPrecision.Int => buffer.putInt(a(i))

        IArray.unsafeFromArray(buffer.array())

      def decompact(b: CompactedArrayInt, d: Vector[D]): IArray[Int] =
        val size = d.length
        var index: Int = 0
        val result = Array.ofDim[Int](size)

        loop(0, _ < size, _ + 1): i =>
          val dValue = d(i)

          def fromByte(b1: Byte, d: D) = b1.toInt - Byte.MinValue + d.low
          def fromShort(b1: Byte, b2: Byte, d: D) =
            val v = ((b1.toInt & 0xFF) << 8 | (b2.toInt & 0xFF)).asInstanceOf[Short]
            v.toInt - Short.MinValue + d.low

          def fromInt(b1: Byte, b2: Byte, b3: Byte, b4: Int) =
            (b(index) << 24) & 0xff000000 | (b(index + 1) << 16) & 0x00ff0000 | (b(index + 2) << 8) & 0x0000ff00 | (b(index + 3) << 0) & 0x000000ff

          dValue.precision match
            case D.IntegerPrecision.Byte => result(i) = fromByte(b(index), dValue)
            case D.IntegerPrecision.Short => result(i) = fromShort(b(index), b(index + 1), dValue)
            case D.IntegerPrecision.Int => result(i) = fromInt(b(index), b(index + 1), b(index + 2), b(index + 3))

          index = index + dValue.precision.size

        IArray.unsafeFromArray(result)

    opaque type CompactedArrayInt = IArray[Byte]


object deterministic:

  private def evaluation[S, G, I](genomes: Vector[G], expression: G => I, parallel: Algorithm.ParallelContext) =
    parallel match
      case Algorithm.Sequential => genomes.map(expression)
      case context: Algorithm.Parallel =>
        import scala.concurrent
        import scala.concurrent.*
        import duration.*

        given concurrent.ExecutionContext = context.executionContext
        val futures = genomes.map(g => Future(expression(g)))
        Await.result(Future.sequence(futures), Duration.Inf)

  def initialPopulation[G, I](
    initialGenomes: Vector[G],
    expression: (G, Long, Boolean) => I,
    parallel: Algorithm.ParallelContext): Vector[I] =
    evaluation(initialGenomes, expression(_, 0, true), parallel)

  def step[S, I, G](
    breeding: Breeding[S, I, G],
    expression: (G, Long, Boolean) => I,
    elitism: Elitism[S, I],
    generation: monocle.Lens[S, Long],
    evaluated: monocle.Lens[S, Long])(s: S, population: Vector[I], rng: scala.util.Random, parallel: Algorithm.ParallelContext): (S, Vector[I]) =
    val newGenomes = breeding(s, population, rng)
    val newPopulation = evaluation(newGenomes, expression(_, generation.get(s), false), parallel)
    val (s2, elitePopulation) = elitism(s, population, newPopulation, rng)
    val s3 = generation.modify(_ + 1)(s2)
    val s4 = evaluated.modify(_ + newGenomes.size)(s3)
    (s4, elitePopulation)

  def expression[G, P, I](
    values: G => (IArray[Double], IArray[Int]),
    build: (G, P, Long, Boolean) => I,
    fitness: (IArray[Double], IArray[Int]) => P) =
    (g: G, generation: Long, initial: Boolean) =>
      val (cs, ds) = values(g)
      build(g, fitness(cs, ds), generation, initial)


object noisy:
  private def evaluation[G, I](expression: (util.Random, G) => I, genomes: Vector[G], rng: Random, parallel: Algorithm.ParallelContext) =
    parallel match
      case Algorithm.Sequential =>
        def evaluate(g: G) = expression(rng, g)
        genomes.map(evaluate)
      case context: Algorithm.Parallel =>
        import scala.concurrent
        import scala.concurrent.*
        import duration.*

        given concurrent.ExecutionContext = context.executionContext

        val futures =
          (genomes zip Iterator.continually(rng.nextLong).map(context.seeder)).map: (g, rng) =>
            Future(expression(rng, g))

        Await.result(Future.sequence(futures), Duration.Inf)

  def initialPopulation[G, I](
    initialGenomes: Vector[G],
    expression: (util.Random, G, Long, Boolean) => I,
    rng: scala.util.Random,
    parallel: Algorithm.ParallelContext): Vector[I] =
    evaluation(expression(_, _, 0, true), initialGenomes, rng, parallel)

  def step[S, I, G](
    breeding: Breeding[S, I, G],
    expression: (util.Random, G, Long, Boolean) => I,
    elitism: Elitism[S, I],
    generation: monocle.Lens[S, Long],
    evaluated: monocle.Lens[S, Long])(s: S, population: Vector[I], rng: scala.util.Random, parallel: Algorithm.ParallelContext): (S, Vector[I]) =

    val newGenomes = breeding(s, population, rng)
    val newPopulation = evaluation(expression(_, _, generation.get(s), false), newGenomes, rng, parallel)

    val (s2, elitePopulation) = elitism(s, population, newPopulation, rng)
    val s3 = generation.modify(_ + 1)(s2)
    val s4 = evaluated.modify(_ + newGenomes.size)(s3)

    (s4, elitePopulation)

  def expression[G, I, P](
    values: G => (IArray[Double], IArray[Int]),
    build: (G, P, Long, Boolean) => I)(phenotype: (util.Random, IArray[Double], IArray[Int]) => P) =
    (rg: util.Random, g: G, generation: Long, initial: Boolean) =>
      val (cs, ds) = values(g)
      build(g, phenotype(rg, cs, ds), generation, initial)


type HitMap = Map[Vector[Int], Int]








© 2015 - 2025 Weber Informatics LLC | Privacy Policy