10

I'm trying to code the fast Non Dominated Sorting algorithm (NDS) of Deb used in NSGA2 in immutable way using Scala.

enter image description here

But the problem seems more difficult than i think, so i simplify here the problem to make a MWE.

Imagine a population of Seq[A], and each A element is decoratedA with a list which contains pointers to other elements of the population Seq[A].

A function evalA(a:decoratedA) take the list of linkedA it contains, and decrement value of each.

Next i take a subset list decoratedAPopulation of population A, and call evalA on each. I have a problem, because between each iteration on element on this subset list decoratedAPopulation, i need to update my population of A with the new decoratedA and the new updated linkedA it contain ...

More problematic, each element of population need an update of 'linkedA' to replace the linked element if it change ...

enter image description here

Hum as you can see, it seem complicated to maintain all linked list synchronized in this way. I propose another solution bottom, which probably need recursion to return after each EvalA a new Population with element replaced.

enter image description here

How can i do that correctly in an immutable way ?

It's easy to code in a mutable way, but i don't find a good way to do this in an immutable way, do you have a path or an idea to do that ?

object test extends App{

  case class A(value:Int) {def decrement()= new A(value - 1)}

  case class decoratedA(oneAdecorated:A, listOfLinkedA:Seq[A])

  // We start algorithm loop with A element with value = 0
  val population = Seq(new A(0), new A(0), new A(8), new A(1))

  val decoratedApopulation = Seq(new decoratedA(population(1),Seq(population(2),population(3))),
                                 new decoratedA(population(2),Seq(population(1),population(3))))
  def evalA(a:decoratedA) = {
    val newListOfLinked = a.listOfLinkedA.map{e => e.decrement()
    new decoratedA(a.oneAdecorated,newListOfLinked)}
  }

  def run()= {
    //decoratedApopulation.map{
    // ?
    //}
  }
} 

Update 1:

About the input / output of the initial algorithm.

The first part of Deb algorithm (Step 1 to Step 3) analyse a list of Individual, and compute for each A : (a) domination count, the number of A which dominate me (the value attribute of A) (b) a list of A i dominate (listOfLinkedA).

So it return a Population of decoratedA totally initialized, and for the entry of Step 4 (my problem) i take the first non dominated front, cf. the subset of elements of decoratedA with A value = 0.

My problem start here, with a list of decoratedA with A value = 0; and i search the next front into this list by computing each listOfLinkedA of each of this A

At each iteration between step 4 to step 6, i need to compute a new B subset list of decoratedA with A value = 0. For each , i decrementing first the domination count attribute of each element into listOfLinkedA, then i filter to get the element equal to 0. A the end of step 6, B is saved to a list List[Seq[DecoratedA]], then i restart to step 4 with B, and compute a new C, etc.

Something like that in my code, i call explore() for each element of B, with Q equal at the end to new subset of decoratedA with value (fitness here) = 0 :

case class PopulationElement(popElement:Seq[Double]){
  implicit def poptodouble():Seq[Double] = {
    popElement
  }
}

class SolutionElement(values: PopulationElement, fitness:Double, dominates: Seq[SolutionElement])  {

  def decrement()= if (fitness == 0) this else new SolutionElement(values,fitness - 1, dominates)

  def explore(Q:Seq[SolutionElement]):(SolutionElement, Seq[SolutionElement])={
    // return all dominates elements with fitness - 1
    val newSolutionSet = dominates.map{_.decrement()}
    val filteredSolution:Seq[SolutionElement] = newSolutionSet.filter{s => s.fitness == 0.0}.diff{Q}
    filteredSolution

  }
}

A the end of algorithm, i have a final list of seq of decoratedA List[Seq[DecoratedA]] which contain all my fronts computed.

Update 2

A sample of value extracted from this example. I take only the pareto front (red) and the {f,h,l} next front with dominated count = 1.

enter image description here

case class p(x: Int, y: Int)

val a = A(p(3.5, 1.0),0) 
val b = A(p(3.0, 1.5),0) 
val c = A(p(2.0, 2.0),0) 
val d = A(p(1.0, 3.0),0) 
val e = A(p(0.5, 4.0),0) 
val f = A(p(0.5, 4.5),1) 
val h = A(p(1.5, 3.5),1) 
val l = A(p(4.5, 1.0),1) 

case class A(XY:p, value:Int) {def decrement()= new A(XY, value - 1)}

case class ARoot(node:A, children:Seq[A])

val population = Seq(
  ARoot(a,Seq(f,h,l),
  ARoot(b,Seq(f,h,l)),
  ARoot(c,Seq(f,h,l)),   
  ARoot(d,Seq(f,h,l)),
  ARoot(e,Seq(f,h,l)),
  ARoot(f,Nil),
  ARoot(h,Nil),
  ARoot(l,Nil))

Algorithm return List(List(a,b,c,d,e), List(f,h,l))

Update 3

After 2 hour, and some pattern matching problems (Ahum...) i'm comming back with complete example which compute automaticaly the dominated counter, and the children of each ARoot.

But i have the same problem, my children list computation is not totally correct, because each element A is possibly a shared member of another ARoot children list, so i need to think about your answer to modify it :/ At this time i only compute children list of Seq[p], and i need list of seq[A]

  case class p(x: Double, y: Double){
  def toSeq():Seq[Double] = Seq(x,y)
}

case class A(XY:p, dominatedCounter:Int) {def decrement()= new A(XY, dominatedCounter - 1)}

case class ARoot(node:A, children:Seq[A])
case class ARootRaw(node:A, children:Seq[p])

object test_stackoverflow extends App {

  val a = new p(3.5, 1.0)
  val b = new p(3.0, 1.5)
  val c = new p(2.0, 2.0)
  val d = new p(1.0, 3.0)
  val e = new p(0.5, 4.0)
  val f = new p(0.5, 4.5)
  val g = new p(1.5, 4.5)
  val h = new p(1.5, 3.5)
  val i = new p(2.0, 3.5)
  val j = new p(2.5, 3.0)
  val k = new p(3.5, 2.0)
  val l = new p(4.5, 1.0)
  val m = new p(4.5, 2.5)
  val n = new p(4.0, 4.0)
  val o = new p(3.0, 4.0)
  val p = new p(5.0, 4.5)

  def isStriclyDominated(p1: p, p2: p): Boolean = {
    (p1.toSeq zip p2.toSeq).exists { case (g1, g2) => g1 < g2 }
  }

  def sortedByRank(population: Seq[p]) = {

    def paretoRanking(values: Set[p]) = {
      //comment from @dk14: I suppose order of values isn't matter here, otherwise use SortedSet

      values.map { v1 =>
        val t = (values - v1).filter(isStriclyDominated(v1, _)).toSeq
        val a = new A(v1, values.size - t.size - 1)
        val root = new ARootRaw(a, t)
        println("Root value ", root)
        root
      }
    }

    val listOfARootRaw = paretoRanking(population.toSet)
    //From @dk14: Here is convertion from Seq[p] to Seq[A]
    val dominations: Map[p, Int] = listOfARootRaw.map(a => a.node.XY -> a.node.dominatedCounter) //From @dk14: It's a map with dominatedCounter for each point
    val listOfARoot = listOfARootRaw.map(raw =>  ARoot(raw.node, raw.children.map(p => A(p, dominations.getOrElse(p, 0)))))

    listOfARoot.groupBy(_.node.dominatedCounter)

  }

  //Get the first front, a subset of ARoot, and start the step 4
  println(sortedByRank(Seq(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)).head)

}
dk14
  • 22,206
  • 4
  • 51
  • 88
reyman64
  • 523
  • 4
  • 34
  • 73
  • 1
    You could use a recursive function that gets as parameter the population list. That way you can give the next iteration an updated list without changing the original. – Kraylog Apr 10 '15 at 05:54
  • Yes i also try with recursion, the problem is how can i search and replace efficiently the elements in pop in immutable way.. – reyman64 Apr 10 '15 at 07:04
  • Let me understand the requirements. You need some sort of data structure(s), that is a sequence (order is important), quick search, quick replace, and immutable. So far correct? – Kraylog Apr 10 '15 at 14:25
  • Yes it seem correct, as you can see on the figure, it's probably impossible to maintain all linked element synchronised. So replacing each linked list of object by linked list of pointers (like number) on element into an indexedSeq representing population is probably a better way to do that, isn't it ? – reyman64 Apr 10 '15 at 14:36
  • I update with a new image which explain a second way to do that, using recursion as you say; and quick search and replace of multipe elements in a population – reyman64 Apr 10 '15 at 14:49
  • updated your update3, sorry for doing that right in your question :) – dk14 Apr 12 '15 at 17:24
  • No problem, i see that and learn *a lot* from your code modification, so thanks ! – reyman64 Apr 12 '15 at 17:25
  • You're welcome. It could be rewritten more better, but have no time today – dk14 Apr 12 '15 at 17:26
  • @dk14 I make some minor correction of typo, but i don't understand the `getOrElse(p,0)` part and error, because option is remove by flatten before ? – reyman64 Apr 13 '15 at 16:58
  • `dominations: Map[p, Int]` contains weights for each point - `getOrElse` returns weight for point - or zero if it's not in the map; `t` has `Set[Option[p]]` type after `.map(Some)` - `map(_.filter)` doesn't remove option it just turns all `Some`s (created by `.map(Some) into `Some`s and `None`s (where `None`s is not dominated points). – dk14 Apr 13 '15 at 17:43
  • by the way it might be done without `Option`s and flattening, as they needed just to count non-filtered points - see my update – dk14 Apr 13 '15 at 17:46
  • About `getOrElse` again - if you just need to propagate `dominatedCount` from let's say `ARoot(p(0,0), someChilden)` to all `p(0,0)`'s of all `children`s (of al roots) - just store this value into the Map (dictionary) with key `p(0,0)`, traverse all children of all roots and replace point with `A(point, valueForThisPointFromTheMap)`. Same approach if you need to mutate some data after (calculate new states, put it to the map, traverse `ARoot`s+children and replace from the Map). – dk14 Apr 13 '15 at 18:03
  • Hum, i start to understand the concept, i have one last question about code sample `Map[p,Int]` dominations line : my compiler say on this code `Expression of type Set[(p,Int)] doesn't conforme to expected type Map[p,Int]` – reyman64 Apr 14 '15 at 08:31

1 Answers1

3

Talking about your problem with distinguishing fronts (after update 2):

val (left,right) = population.partition(_.node.value == 0)
List(left, right.map(_.copy(node = node.copy(value = node.value - 1))))

No need for mutating anything here. copy will copy everything but fields you specified with new values. Talking about the code, the new copy will be linked to the same list of children, but new value = value - 1.

P.S. I have a feeling you may actually want to do something like this:

case class A(id: String, level: Int)
val a = A("a", 1)
val b = A("b", 2)
val c = A("c", 2)
val d = A("d", 3)

clusterize(List(a,b,c,d)) === List(List(a), List(b,c), List(d))

It's simple to implement:

def clusterize(list: List[A]) = 
   list.groupBy(_.level).toList.sortBy(_._1).map(_._2)

Test:

scala> clusterize(List(A("a", 1), A("b", 2), A("c", 2), A("d", 3)))
res2: List[List[A]] = List(List(A(a,1)), List(A(b,2), A(c,2)), List(A(d,3)))

P.S.2. Please consider better naming conventions, like here.


Talking about "mutating" elements in some complex structure:

The idea of "immutable mutating" some shared (between parts of a structure) value is to separate your "mutation" from the structure. Or simply saying, divide and conquerror:

  1. calculate changes in advance
  2. apply them

The code:

 case class A(v: Int)
 case class AA(a: A, seq: Seq[A]) //decoratedA

 def update(input: Seq[AA]) = {
    //shows how to decrement each value wherever it is:
    val stats = input.map(_.a).groupBy(identity).mapValues(_.size) //domination count for each A
    def upd(a: A) = A(a.v - stats.getOrElse(a, 0)) //apply decrement
    input.map(aa => aa.copy(aa = aa.seq.map(upd))) //traverse and "update" original structure
 }

So, I've introduced new Map[A, Int] structure, that shows how to modify the original one. This approach is based on highly simplified version of Applicative Functor concept. In general case, it should be Map[A, A => A] or even Map[K, A => B] or even Map[K, Zipper[A] => B] as applicative functor (input <*> map). *Zipper (see 1, 2) actually could give you information about current element's context.

Notes:

  • I assumed that As with same value are same; that's default behaviour for case classess, otherwise you need to provide some additional id's (or redefine hashCode/equals).

  • If you need more levels - like AA(AA(AA(...)))) - just make stats and upd recursive, if dеcrement's weight depends on nesting level - just add nesting level as parameter to your recursive function.

  • If decrement depends on parent node (like decrement only A(3)'s, which belongs to A(3)) - add parent node(s) as part of stats's key and analise it during upd.

  • If there is some dependency between stats calculation (how much to decrement) of let's say input(1) from input(0) - you should use foldLeft with partial stats as accumulator: val stats = input.foldLeft(Map[A, Int]())((partialStats, elem) => partialStats ++ analize(partialStats, elem))

Btw, it takes O(N) here (linear memory and cpu usage)

Example:

scala> val population = Seq(A(3), A(6), A(8), A(3))
population: Seq[A] = List(A(3), A(6), A(8), A(3))

scala> val input = Seq(AA(population(1),Seq(population(2),population(3))), AA(population(2),Seq(population(1),population(3))))
input: Seq[AA] = List(AA(A(6),List(A(8), A(3))), AA(A(8),List(A(6), A(3))))

scala> update(input)
res34: Seq[AA] = List(AA(A(5),List(A(7), A(3))), AA(A(7),List(A(5), A(3))))
Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88
  • I update with some details on algorithm input output – reyman64 Apr 12 '15 at 08:24
  • could you please just add examples of the input and output, like in the answer – dk14 Apr 12 '15 at 08:26
  • I mean what the part of agorithm we're talking about should return for this input: `List(decoratedA(A(6),List(A(8), A(3))), decoratedA(A(8),List(A(6), A(3))))` ? – dk14 Apr 12 '15 at 08:30
  • My solution doesn't affect your initial algorithm - it's still `Seq[DecoratedA] => Seq[DecoratedA]` - I just want to be sure that I've decremented `A` elements correctly. From my understanding, I should decrement `A.value -= N`, where N is count of same `A`'s inside `Seq[DecoratedA]` (including linked elements). Your approach seems to be trying to mutate `A` every time you see same A - I'm proposing to count them in advance, and then - decrement (to avoid unnecessary recursion leading to the O(N*N)). – dk14 Apr 12 '15 at 08:51
  • Multiple `A`s in the population can have same value, because this is only a count, so i probably need to add an `id` if i understand well your answer. About value of `A`s, this is dyssemetric, n is not equal to the count of linked list. For example `A` can be dominated by 5 other `A`, and dominates 3 others. This is another function which compute these two list for each `A` during the step 1 to step 3. Rest of algorithm only decrement `value` of `A` element i think and never recompute LinkedList. – reyman64 Apr 12 '15 at 09:00
  • So what is N - dominating or dominatees count, how much should I distract from value ? – dk14 Apr 12 '15 at 09:02
  • Let's say, I have some A1 with value 6. It's represented as a root node (`oneAdecorated`) 2 times (dominates) and represented as a leaf (something inside `listOfLinkedA` ) 3 times (dominated). Which value A1 should have at the end of `Seq[DecoratedA] => Seq[DecoratedA]` - 3 or 4? – dk14 Apr 12 '15 at 09:06
  • Oh, I see it's domination count – dk14 Apr 12 '15 at 09:12
  • Yes it's domination count, in your example if A1 value is 6, that indicated that 6 others A dominates me. When it's equal to 0 (after many iteration i suppose), i save the A to the new subset for the next iteration. – reyman64 Apr 12 '15 at 09:13
  • I've corrected my answer a bit (removed dominatee's counting), about "id's" - it's actually a feature of case classes to generate `hashCode` based on content, so you could ignore it by using regular classes; however, having some readable `id`s is always a good idea – dk14 Apr 12 '15 at 09:17
  • Hum, i understand where is the problem for comprehension into my question, my example is not correct, arg, sorry. I update, the idea is that the A element in entry have value = 0, it's logic. Only LinkedList element are decremented. i correct example, sorry. – reyman64 Apr 12 '15 at 09:23
  • You know - it's simler if you just give both **data samples** for input and respective **output** :). If I know what outupt you're looking for some given input (test) - I could provide you correct solution, regardless that idea will be still the same - divide and conquerror: 1) calculate changes in advance; 2) apply them – dk14 Apr 12 '15 at 09:27
  • I prepare an update 2, i will be back in some couple of minutes :) – reyman64 Apr 12 '15 at 09:31
  • I update with a first sample, i complete later in the day. Hope that helps – reyman64 Apr 12 '15 at 10:03