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

mgo.tools.metric.Hypervolume.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 Sebastien Rey
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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.tools.metric

import cats._
import mgo.evolution.dominance._
import mgo.tools._

import scala.collection.mutable.{ ArrayBuffer, IndexedSeq => MIndexedSeq }
import scala.math._
import scala.collection.mutable

// A translation/adaptation based on the python source code by Simon Wessing :
// http://ls11-www.cs.uni-dortmund.de/_media/rudolph/hypervolume/hv_python.zip

/**
 * Hypervolume computation based on variant 3 of the algorithm in the paper:
 * C. M. Fonseca, L. Paquete, and M. Lopez-Ibanez. An improved dimension-sweep
 * algorithm for the hypervolume indicator. In IEEE Congress on Evolutionary
 * Computation, pages 1157-1163, Vancouver, Canada, July 2006.
 *
 * FIXE: The implementation is ugly, as the algorithm as directly been translated
 * from python
 *
 */
object Hypervolume {

  /**
   * Compute the hypervolume contribution for each front
   */
  def contributions(front: Vector[Vector[Double]], referencePoint: Vector[Double]): Vector[Later[Double]] = {

    lazy val globalHypervolume = Hypervolume(front, referencePoint)

    //compute a new collection with automatic removed incremental of frontValues item by item
    front.shadows.map {
      case (e) => Later(globalHypervolume - Hypervolume.apply(e, referencePoint))
    }.toVector
  }

  /**
   * Compute the nadir of a set of points
   *
   * @param points a set of points
   * @return the nadir point
   */
  def nadir(points: Vector[Vector[Double]]): Vector[Double] =
    points.reduce {
      (i1, i2) => (i1 zip i2).map { case (i1, i2) => max(i1, i2) }
    }

  /**
   * Compute the hypervolume that is dominated by a non-dominated front.
   * Before the HV computation, front and reference point are translated, so
   * that the reference point is [0, ..., 0].
   *
   * @param front the parato front
   * @param referencePoint point the reference point for computing the volume from
   * this point to the front
   * @return the hypervolume
   */
  def apply(front: Vector[Vector[Double]], referencePoint: Vector[Double]): Double = {
    def dominates(point: Seq[Double], other: Seq[Double]): Boolean =
      nonStrictDominance.isDominated(other, point)

    val dimensions = referencePoint.size

    val relevantPoints =
      front.filter(dominates(_, referencePoint)).map {
        point => (point zip referencePoint).map { case (p, r) => p - r }
      }

    val list = preProcess(relevantPoints, referencePoint)

    val bounds = MIndexedSeq.fill(dimensions) {
      -1.0e308
    }

    /**
     * Recursive call to hypervolume calculation.
     *
     * In contrast to the paper, the code assumes that the reference point
     * is [0, ..., 0]. This allows the avoidance of a few operations.
     */
    def hvRecursive(dimIndex: Int, length: Int, bounds: MIndexedSeq[Double]): Double = {

      var hvol = 0.0
      var sentinel = list.sentinel
      var newLength = length

      if (newLength == 0) {
        hvol
      } else if (dimIndex == 0) {
        sentinel.next(0) match {
          case None => 0.0
          case Some(n) => -n.cargo(0)
        }
      } else if (dimIndex == 1) {

        //Transform q option to node
        var q: Node = sentinel.next(1).get

        var h: Double = q.cargo(0)
        //Transform p option to node
        var p: Node = q.next(1).get

        while (p != sentinel) {

          var pCargo = p.cargo
          hvol += h * (q.cargo(1) - pCargo(1))
          if (pCargo(0) < h) {
            h = pCargo(0)
          }
          q = p
          p = q.next(1).get
        }
        hvol += h * q.cargo(1)
        hvol
      } else {

        var p: Node = sentinel
        //Transform q option to node
        var q: Node = p.prev(dimIndex).get

        while (!q.cargo.isEmpty) {
          if (q.ignore < dimIndex) {
            q.ignore = 0
          }
          q = q.prev(dimIndex).get
        }

        q = p.prev(dimIndex).get

        while (newLength > 1 && (q.cargo(dimIndex) > bounds(dimIndex) || (q.prev(dimIndex).get).cargo(dimIndex) >= bounds(dimIndex))) {
          p = q
          list.remove(p, dimIndex, bounds)
          q = p.prev(dimIndex).get
          newLength = newLength - 1
        }

        var qPrevDimIndex = q.prev(dimIndex).get
        if (newLength > 1) {
          hvol = qPrevDimIndex.volume(dimIndex) + qPrevDimIndex.area(dimIndex) * (q.cargo(dimIndex) - qPrevDimIndex.cargo(dimIndex))
        } else {
          // CODE POURRI , A REFAIRE car QAREA est un passage par ref en python ... sert a rien
          q.area(0) = 1.0
          q.area = ArrayBuffer(1.0) ++ (Range(0, dimIndex).map {
            i => q.area(i) * -q.cargo(i)
          })
          q.area = q.area

        }

        q.volume(dimIndex) = hvol
        if (q.ignore >= dimIndex) {
          q.area(dimIndex) = qPrevDimIndex.area(dimIndex)
        } else {
          q.area(dimIndex) = hvRecursive(dimIndex - 1, newLength, bounds)

          if (q.area(dimIndex) <= qPrevDimIndex.area(dimIndex)) {
            q.ignore = dimIndex
          }
        }

        while (p != sentinel) {
          var pCargoDimIndex = p.cargo(dimIndex)
          hvol += q.area(dimIndex) * (pCargoDimIndex - q.cargo(dimIndex))
          bounds(dimIndex) = pCargoDimIndex
          list.reinsert(p, dimIndex, bounds)
          newLength += 1
          q = p
          p = p.next(dimIndex).get
          q.volume(dimIndex) = hvol

          if (q.ignore >= dimIndex) {
            q.area(dimIndex) = (q.prev(dimIndex).get).area(dimIndex)
          } else {
            q.area(dimIndex) = hvRecursive(dimIndex - 1, newLength, bounds)
            if (q.area(dimIndex) <= (q.prev(dimIndex).get).area(dimIndex)) {
              q.ignore = dimIndex
            }
          }
        }
        hvol -= q.area(dimIndex) * q.cargo(dimIndex)
        hvol

      }
    }

    //MAIN COMPUTE
    return hvRecursive(dimensions - 1, relevantPoints.size, bounds)
  }

  /* Sets up the list data structure needed for calculation. */
  def preProcess(front: Seq[Seq[Double]], referencePoint: Seq[Double]): MultiList = {
    val dimensions = referencePoint.size
    var nodeList = new MultiList(dimensions)

    var nodes = front.map {
      point => new Node(dimensions, point)
    }

    Range(0, dimensions).map {
      i =>
        nodes = sortByDimension(nodes, i)
        nodeList.extend(nodes, i)
    }
    nodeList
  }

  /* Sorts the list of nodes by the i -th value of the contained points. */
  def sortByDimension(nodes: Seq[Node], i: Int): Seq[Node] = nodes.sortBy(_.cargo(i))

  class Node(numberLists: Int, val cargo: Seq[Double] = IndexedSeq.empty) {

    var next: MIndexedSeq[Option[Node]] = MIndexedSeq.fill(numberLists) { None }
    var prev: MIndexedSeq[Option[Node]] = MIndexedSeq.fill(numberLists) { None }
    var ignore = 0

    var area: mutable.IndexedSeq[Double] = MIndexedSeq.fill(numberLists) {
      0.0
    }

    var volume: mutable.IndexedSeq[Double] = MIndexedSeq.fill(numberLists) {
      0.0
    }

    override def toString = cargo.toString

  }

  /**
   * A special data structure needed by FonsecaHyperVolume.
   *
   * It consists of several doubly linked lists that share common nodes. So,
   * every node has multiple predecessors and successors, one in every list.
   */
  class MultiList(numberLists: Int) {

    var sentinel = new Node(numberLists)
    sentinel.next = MIndexedSeq.fill(numberLists) {
      Some(sentinel)
    }
    sentinel.prev = MIndexedSeq.fill(numberLists) {
      Some(sentinel)
    }

    /* Returns the number of lists that are included in this MultiList. */
    def len = numberLists

    /* Returns the length of the i-th list. */
    def getLength(i: Int): Int = {
      var length = 0
      var node = sentinel.next(i)

      while (node.get != sentinel) {
        length += 1
        node = node.get.next(i)
      }
      length
    }

    /* Appends a node to the end of the list at the given index. */
    def append(node: Node, index: Int): Unit = {
      val lastButOne = sentinel.prev(index)
      node.next(index) = Some(sentinel)
      node.prev(index) = lastButOne //set the last element as the new one
      sentinel.prev(index) = Some(node)
      lastButOne match {
        case None =>
        case Some(n) => n.next(index) = Some(node)
      }
    }

    /* Extends the list at the given index with the nodes. */
    def extend(nodes: Seq[Node], index: Int): Unit = {

      for (node <- nodes) {
        val lastButOne = sentinel.prev(index)
        node.next(index) = Some(sentinel)
        node.prev(index) = lastButOne //set the last element as the new one
        sentinel.prev(index) = Some(node)
        lastButOne match {
          case None =>
          case Some(n) => n.next(index) = Some(node)
        }
      }
    }

    /* Removes and returns 'node' from all lists in [0, 'index'[. */
    def remove(node: Node, index: Int, bounds: MIndexedSeq[Double]): Unit = {
      for (i <- Range(0, index)) {
        val predecessor = node.prev(i)
        val successor = node.next(i)

        predecessor match {
          case None =>
          case Some(n) => n.next(i) = successor
        }

        successor match {
          case None =>
          case Some(n) => n.prev(i) = predecessor
        }

        if (bounds(i) > node.cargo(i)) {
          bounds(i) = node.cargo(i)
        }

      }
    }

    /**
     * Inserts 'node ' at the position it had in all lists in[ 0, 'index '[
     * before it was removed.This method assumes that the next and previous
     * nodes of the node that is reinserted are in the list.
     */
    def reinsert(node: Node, index: Int, bounds: MIndexedSeq[Double]): Unit = {
      for (i <- Range(0, index)) {

        node.prev(i) match {
          case None =>
          case Some(n) => n.next(i) = Some(node)
        }

        node.next(i) match {
          case None =>
          case Some(n) => n.prev(i) = Some(node)
        }

        if (bounds(i) > node.cargo(i)) {
          bounds(i) = node.cargo(i)
        }
      }

    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy