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

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

The newest version!
package mgo.evolution.algorithm

/*
 * Copyright (C) 2024 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */


import cats.implicits._
import mgo.evolution._
import mgo.evolution.algorithm.GenomeVectorDouble._
import mgo.evolution.algorithm.OSEOperation.ReachMap
import mgo.evolution.breeding._
import mgo.evolution.elitism._
import mgo.evolution.ranking._
import mgo.tools.*

import monocle.function
import monocle._
import monocle.syntax.all._

import scala.reflect.ClassTag

object NoisyHDOSE:
  import CDGenome.*
  import NoisyIndividual.*

  case class StateType[P](archive: Archive[Individual[P]], distance: Double)
  type HDOSEState[P] = EvolutionState[StateType[P]]

  def archiveLens[P]: Lens[EvolutionState[StateType[P]], Archive[Individual[P]]] = Focus[EvolutionState[StateType[P]]](_.s.archive)
  def distanceLens[P]: Lens[HDOSEState[P], Double] = Focus[HDOSEState[P]](_.s.distance)

  def initialState[P](distance: Double = 1.0): HDOSEState[P] = EvolutionState(s = StateType(Archive.empty, distance))

  def initialGenomes(lambda: Int, continuous: Vector[C], discrete: Vector[D], reject: Option[Genome => Boolean], rng: scala.util.Random): Vector[Genome] =
    CDGenome.initialGenomes(lambda, continuous, discrete, reject, rng)

  def adaptiveBreeding[P: Manifest](
    lambda: Int,
    operatorExploration: Double,
    cloneProbability: Double,
    aggregation: Vector[P] => Vector[Double],
    continuous: Vector[C],
    discrete: Vector[D],
    weightC: Vector[Double],
    weightD: Vector[Double],
    limit: Vector[Double],
    reject: Option[Genome => Boolean]): Breeding[HDOSEState[P], Individual[P], Genome] =
    NoisyHDOSEOperations.adaptiveBreeding[HDOSEState[P], Individual[P], Genome, P](
      vectorPhenotype[P].get,
      aggregation,
      Focus[Individual[P]](_.genome).get,
      continuousValues(continuous).get,
      continuousOperator.get,
      discreteValues(discrete).get,
      discreteOperator.get,
      discrete,
      scaledValues(continuous, discrete),
      HDOSE.tooCloseByComponent(weightC, weightD, discrete),
      distanceLens.get,
      buildGenome(discrete),
      logOfPopulationSize,
      lambda,
      reject,
      operatorExploration,
      cloneProbability,
      limit,
      archiveLens.get)

  def expression[P: Manifest](fitness: (util.Random, IArray[Double], IArray[Int]) => P, continuous: Vector[C], discrete: Vector[D]) =
    NoisyIndividual.expression[P](fitness, continuous, discrete)

  def elitism[P: Manifest](
    mu: Int,
    historySize: Int,
    aggregation: Vector[P] => Vector[Double],
    continuous: Vector[C],
    discrete: Vector[D],
    weightC: Vector[Double],
    weightD: Vector[Double],
    archiveSize: Int,
    limit: Vector[Double],
    precision: Double): Elitism[HDOSEState[P], Individual[P]] =
    def individualValues(i: Individual[P]) = scaledVectorValues(continuous, discrete)(i.genome)

    NoisyHDOSEOperations.elitism[HDOSEState[P], Individual[P], P, Genome](
      _.genome,
      vectorPhenotype[P].get,
      aggregation,
      continuousValues(continuous).get,
      discreteValues(discrete).get,
      limit,
      historySize,
      mergeHistories(individualValues, vectorPhenotype[P], Focus[Individual[P]](_.historyAge), historySize),
      mu,
      archiveLens,
      HDOSE.tooCloseByComponent(weightC, weightD, discrete),
      precision,
      distanceLens,
      archiveSize
    )


  case class Result[P](continuous: Vector[Double], discrete: Vector[Int], fitness: Vector[Double], replications: Int, individual: Individual[P], archive: Boolean)

  def result[P: Manifest](state: HDOSEState[P], population: Vector[Individual[P]], aggregation: Vector[P] => Vector[Double], continuous: Vector[C], discrete: Vector[D], limit: Vector[Double], keepAll: Boolean): Vector[Result[P]] =
    def goodIndividuals =
      population.flatMap: i =>
        val (c, d, f, r) = NoisyIndividual.aggregate[P](i, aggregation, continuous, discrete)
        if keepAll || OSEOperation.patternIsReached(f, limit)
        then Some(Result(c, d, f, r, i, false))
        else None

    state.s.archive.toVector.map: i =>
      val (c, d, f, r) = NoisyIndividual.aggregate(i, aggregation, continuous, discrete)
      Result(c, d, f, r, i, true)
    ++ goodIndividuals

  def result[P: Manifest](noisyHDOSE: NoisyHDOSE[P], state: HDOSEState[P], population: Vector[Individual[P]]): Vector[Result[P]] =
    result[P](state, population, noisyHDOSE.aggregation, noisyHDOSE.continuous, noisyHDOSE.discrete, noisyHDOSE.limit, keepAll = false)

  def reject[P](t: NoisyHDOSE[P]): Option[Genome => Boolean] = NSGA2.reject(t.reject, t.continuous, t.discrete)

  given [P: Manifest]: Algorithm[NoisyHDOSE[P], Individual[P], Genome, HDOSEState[P]] with
    def initialState(t: NoisyHDOSE[P], rng: scala.util.Random) = NoisyHDOSE.initialState(t.distance)
    
    def initialPopulation(t: NoisyHDOSE[P], rng: scala.util.Random, parallel: Algorithm.ParallelContext) =
      noisy.initialPopulation[Genome, Individual[P]](
        NoisyOSE.initialGenomes(t.lambda, t.continuous, t.discrete, reject(t), rng),
        NoisyOSE.expression[P](t.fitness, t.continuous, t.discrete),
        rng,
        parallel)

    def step(t: NoisyHDOSE[P]) =
      val wC = t.weightC.getOrElse(Vector.fill(t.continuous.size)(1.0))
      val wD = t.weightD.getOrElse(Vector.fill(t.discrete.size)(1.0))

      noisy.step[HDOSEState[P], Individual[P], Genome](
        NoisyHDOSE.adaptiveBreeding[P](
          t.lambda,
          t.operatorExploration,
          t.cloneProbability,
          t.aggregation,
          t.continuous,
          t.discrete,
          wC,
          wD,
          t.limit,
          reject(t)),
        NoisyHDOSE.expression(t.fitness, t.continuous, t.discrete),
        NoisyHDOSE.elitism[P](
          t.mu,
          t.historySize,
          t.aggregation,
          t.continuous,
          t.discrete,
          wC,
          wD,
          t.archiveSize,
          t.limit,
          t.distance),
        Focus[HDOSEState[P]](_.generation),
        Focus[HDOSEState[P]](_.evaluated)
      )



case class NoisyHDOSE[P](
  mu: Int,
  lambda: Int,
  fitness: (util.Random, IArray[Double], IArray[Int]) => P,
  limit: Vector[Double],
  archiveSize: Int,
  aggregation: Vector[P] => Vector[Double],
  continuous: Vector[C] = Vector.empty,
  discrete: Vector[D] = Vector.empty,
  weightC: Option[Vector[Double]] = None,
  weightD: Option[Vector[Double]] = None,
  historySize: Int = 100,
  cloneProbability: Double = 0.2,
  operatorExploration: Double = 0.1,
  reject: Option[(IArray[Double], IArray[Int]) => Boolean] = None,
  distance: Double = 1.0)

object NoisyHDOSEOperations:


  def adaptiveBreeding[S, I: ClassTag, G, P](
    history: I => Vector[P],
    aggregation: Vector[P] => Vector[Double],
    genome: I => G,
    continuousValues: G => IArray[Double],
    continuousOperator: G => Option[Int],
    discreteValues: G => IArray[Int],
    discreteOperator: G => Option[Int],
    discrete: Vector[D],
    scaledValues: G => (IArray[Double], IArray[Int]),
    distance: HDOSEOperation.TooClose,
    diversityDistance: S => Double,
    buildGenome: (IArray[Double], Option[Int], IArray[Int], Option[Int]) => G,
    tournamentRounds: Int => Int,
    lambda: Int,
    reject: Option[G => Boolean],
    operatorExploration: Double,
    cloneProbability: Double,
    limit: Vector[Double],
    archive: S => Archive[I]): Breeding[S, I, G] =
    (s, population, rng) =>
      val archivedPopulation = archive(s)

      val memoizedFitness = NoisyOSEOperations.aggregated(history, aggregation).memoized
      val value = (continuousValues, discreteValues).tupled
      val genomeValue = (genome andThen value).memoized

      def promising: Vector[I] =
        population.filter(i => OSEOperation.patternIsReached(memoizedFitness(i), limit))

      val tooCloseFromArchiveOrPromising =
        HDOSEOperation.isTooCloseFromArchive(
          distance,
          archivedPopulation ++ promising,
          genomeValue,
          diversityDistance(s))


      val ranks = ranking.paretoRankingMinAndCrowdingDiversity[I](population, memoizedFitness, rng)
      val allRanks = ranks ++ Vector.fill(archivedPopulation.size)(worstParetoRanking)
      val continuousOperatorStatistics = operatorProportions(genome andThen continuousOperator, population)
      val discreteOperatorStatistics = operatorProportions(genome andThen discreteOperator, population)

      def breeding: Breeding[S, I, G] =
        (s, pop, g) =>
          val breed = applyDynamicOperators[S, I, G](
            tournament(allRanks, tournamentRounds),
            genomeValue,
            continuousOperatorStatistics,
            discreteOperatorStatistics,
            discrete,
            operatorExploration,
            buildGenome)(s, pop, rng) //apply ()

          breed.filterNot(g => tooCloseFromArchiveOrPromising(value(g)))

      val offspring = breed(breeding, lambda, reject)(s, population ++ archivedPopulation, rng)
      val sizedOffspringGenomes = randomTake[G](offspring, lambda, rng)
      clonesReplace(cloneProbability, population, genome, tournament(ranks, tournamentRounds))(s, sizedOffspringGenomes, rng)


  def elitism[S, I: ClassTag, P, G](
    genome: I => G,
    history: I => Vector[P],
    aggregation: Vector[P] => Vector[Double],
    continuousValues: G => IArray[Double],
    discreteValues: G => IArray[Int],
    limit: Vector[Double],
    historySize: Int,
    mergeHistories: (Vector[I], Vector[I]) => Vector[I],
    mu: Int,
    archive: monocle.Lens[S, Archive[I]],
    distance: HDOSEOperation.TooClose,
    precision: Double,
    diversityDistance: monocle.Lens[S, Double],
    archiveSize: Int): Elitism[S, I] =
    (s1, population, candidates, rng) =>

      val memoizedFitness = NoisyOSEOperations.aggregated(history, aggregation).memoized
      val merged = filterNaN(mergeHistories(population, candidates), memoizedFitness)
      val genomeValue = (genome andThen (continuousValues, discreteValues).tupled).memoized

      // FIXME individuals can be close to each other but yet added to the archive
      def reachingIndividuals =
        merged.
          filter(i => history(i).size == historySize).
          filter(c => OSEOperation.patternIsReached(memoizedFitness(c), limit))

      val s2 = archive.modify(_ ++ reachingIndividuals)(s1)

      val s3 =
        if archive.get(s2).size <= archiveSize
        then s2
        else
          val newDiversityDistance =
            HDOSEOperation.computeDistance(
              distance,
              archive.get(s2),
              genomeValue,
              archiveSize,
              diversityDistance.get(s2),
              precision
            )

          val newArchive =
            HDOSEOperation.shrinkArchive(
              distance,
              archive.get(s2),
              genomeValue,
              newDiversityDistance
            )

          (archive.replace(newArchive) andThen diversityDistance.replace(newDiversityDistance))(s2)


      val filteredPopulation =
        merged.filterNot: i =>
          HDOSEOperation.isTooCloseFromArchive(
            distance,
            archive.get(s3),
            genomeValue,
            diversityDistance.get(s3))(genomeValue(i))

      NoisyNSGA2Operations.elitism[S, I, P](memoizedFitness, mergeHistories, mu)(s3, filteredPopulation, Vector.empty, rng)






© 2015 - 2025 Weber Informatics LLC | Privacy Policy