1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 __author__ = "Simon Wessing"
19
20 try:
21 xrange = range
22 except:
23 pass
24 from fractions import Fraction as Ff
25
27 """
28 Hypervolume computation based on variant 3 of the algorithm in the paper:
29 C. M. Fonseca, L. Paquete, and M. Lopez-Ibanez. An improved dimension-sweep
30 algorithm for the hypervolume indicator. In IEEE Congress on Evolutionary
31 Computation, pages 1157-1163, Vancouver, Canada, July 2006.
32
33 Minimization is implicitly assumed here!
34
35 """
36
38 """Constructor."""
39 self.referencePoint = referencePoint
40 self.list = []
41
42
44 """Returns the hypervolume that is dominated by a non-dominated front.
45
46 Before the HV computation, front and reference point are translated, so
47 that the reference point is [0, ..., 0].
48
49 """
50
51 def weaklyDominates(point, other):
52 for i in xrange(len(point)):
53 if point[i] > other[i]:
54 return False
55 return True
56
57 relevantPoints = []
58 referencePoint = self.referencePoint
59 dimensions = len(referencePoint)
60 for point in front:
61
62 if weaklyDominates(point, referencePoint):
63 relevantPoints.append(point)
64 if any(referencePoint):
65
66
67
68 for j in xrange(len(relevantPoints)):
69 relevantPoints[j] = [Ff(relevantPoints[j][i]) - Ff(referencePoint[i]) for i in xrange(dimensions)]
70 self.preProcess(relevantPoints)
71 bounds = [-Ff(1.0e308)] * dimensions
72 hyperVolume = self.hvRecursive(dimensions - 1, len(relevantPoints), bounds)
73 return hyperVolume
74
75
77 """Recursive call to hypervolume calculation.
78
79 In contrast to the paper, the code assumes that the reference point
80 is [0, ..., 0]. This allows the avoidance of a few operations.
81
82 """
83 hvol = Ff(0.0)
84 sentinel = self.list.sentinel
85 if length == 0:
86 return hvol
87 elif dimIndex == 0:
88
89
90 return -sentinel.next[0].cargo[0]
91 elif dimIndex == 1:
92
93 q = sentinel.next[1]
94 h = Ff(q.cargo[0])
95 p = q.next[1]
96 while p is not sentinel:
97 pCargo = p.cargo
98 hvol += h * (Ff(q.cargo[1]) - Ff(pCargo[1]))
99 if Ff(pCargo[0]) < h:
100 h = Ff(pCargo[0])
101 q = p
102 p = q.next[1]
103 hvol += h * Ff(q.cargo[1])
104 return hvol
105 else:
106 remove = self.list.remove
107 reinsert = self.list.reinsert
108 hvRecursive = self.hvRecursive
109 p = sentinel
110 q = p.prev[dimIndex]
111 while q.cargo != None:
112 if q.ignore < dimIndex:
113 q.ignore = 0
114 q = q.prev[dimIndex]
115 q = p.prev[dimIndex]
116 while length > 1 and (q.cargo[dimIndex] > bounds[dimIndex] or q.prev[dimIndex].cargo[dimIndex] >= bounds[dimIndex]):
117 p = q
118 remove(p, dimIndex, bounds)
119 q = p.prev[dimIndex]
120 length -= 1
121 qArea = q.area
122 qCargo = q.cargo
123 qPrevDimIndex = q.prev[dimIndex]
124 if length > 1:
125 hvol = Ff(qPrevDimIndex.volume[dimIndex]) + Ff(
126 qPrevDimIndex.area[dimIndex]) * (Ff(qCargo[dimIndex]) - Ff(qPrevDimIndex.cargo[dimIndex]))
127 else:
128 qArea[0] = Ff(1)
129 qArea[1:dimIndex+1] = [Ff(qArea[i]) * Ff(-qCargo[i]) for i in xrange(dimIndex)]
130 q.volume[dimIndex] = hvol
131 if q.ignore >= dimIndex:
132 qArea[dimIndex] = qPrevDimIndex.area[dimIndex]
133 else:
134 qArea[dimIndex] = hvRecursive(dimIndex - 1, length, bounds)
135 if qArea[dimIndex] <= qPrevDimIndex.area[dimIndex]:
136 q.ignore = dimIndex
137 while p is not sentinel:
138 pCargoDimIndex = Ff(p.cargo[dimIndex])
139 hvol += Ff(q.area[dimIndex]) * Ff((pCargoDimIndex - Ff(q.cargo[dimIndex])))
140 bounds[dimIndex] = pCargoDimIndex
141 reinsert(p, dimIndex, bounds)
142 length += 1
143 q = p
144 p = p.next[dimIndex]
145 q.volume[dimIndex] = hvol
146 if q.ignore >= dimIndex:
147 q.area[dimIndex] = Ff(q.prev[dimIndex].area[dimIndex])
148 else:
149 q.area[dimIndex] = hvRecursive(dimIndex - 1, length, bounds)
150 if Ff(q.area[dimIndex]) <= Ff(q.prev[dimIndex].area[dimIndex]):
151 q.ignore = dimIndex
152 hvol -= Ff(q.area[dimIndex]) * Ff(q.cargo[dimIndex])
153 return hvol
154
155
157 """Sets up the list data structure needed for calculation."""
158 dimensions = len(self.referencePoint)
159 nodeList = MultiList(dimensions)
160 nodes = [MultiList.Node(dimensions, point) for point in front]
161 for i in xrange(dimensions):
162 self.sortByDimension(nodes, i)
163 nodeList.extend(nodes, i)
164 self.list = nodeList
165
166
168 """Sorts the list of nodes by the i-th value of the contained points."""
169
170 decorated = [(node.cargo[i], node) for node in nodes]
171
172
173
174
175
176 decorated.sort(key = lambda u: u[0])
177
178
179 nodes[:] = [node for (_, node) in decorated]
180
181
182
184 """A special data structure needed by FonsecaHyperVolume.
185
186 It consists of several doubly linked lists that share common nodes. So,
187 every node has multiple predecessors and successors, one in every list.
188
189 """
190
192
193 - def __init__(self, numberLists, cargo=None):
194 self.cargo = cargo
195 self.next = [None] * numberLists
196 self.prev = [None] * numberLists
197 self.ignore = 0
198 self.area = [Ff(0.0)] * numberLists
199 self.volume = [Ff(0.0)] * numberLists
200
202 return str(self.cargo)
203
204
206 """Constructor.
207
208 Builds 'numberLists' doubly linked lists.
209
210 """
211 self.numberLists = numberLists
212 self.sentinel = MultiList.Node(numberLists)
213 self.sentinel.next = [self.sentinel] * numberLists
214 self.sentinel.prev = [self.sentinel] * numberLists
215
216
218 strings = []
219 for i in xrange(self.numberLists):
220 currentList = []
221 node = self.sentinel.next[i]
222 while node != self.sentinel:
223 currentList.append(str(node))
224 node = node.next[i]
225 strings.append(str(currentList))
226 stringRepr = ""
227 for string in strings:
228 stringRepr += string + "\n"
229 return stringRepr
230
231
233 """Returns the number of lists that are included in this MultiList."""
234 return self.numberLists
235
236
238 """Returns the length of the i-th list."""
239 length = 0
240 sentinel = self.sentinel
241 node = sentinel.next[i]
242 while node != sentinel:
243 length += 1
244 node = node.next[i]
245 return length
246
247
248 - def append(self, node, index):
249 """Appends a node to the end of the list at the given index."""
250 lastButOne = self.sentinel.prev[index]
251 node.next[index] = self.sentinel
252 node.prev[index] = lastButOne
253
254 self.sentinel.prev[index] = node
255 lastButOne.next[index] = node
256
257
258 - def extend(self, nodes, index):
259 """Extends the list at the given index with the nodes."""
260 sentinel = self.sentinel
261 for node in nodes:
262 lastButOne = sentinel.prev[index]
263 node.next[index] = sentinel
264 node.prev[index] = lastButOne
265
266 sentinel.prev[index] = node
267 lastButOne.next[index] = node
268
269
270 - def remove(self, node, index, bounds):
271 """Removes and returns 'node' from all lists in [0, 'index'[."""
272 for i in xrange(index):
273 predecessor = node.prev[i]
274 successor = node.next[i]
275 predecessor.next[i] = successor
276 successor.prev[i] = predecessor
277 if bounds[i] > node.cargo[i]:
278 bounds[i] = Ff(node.cargo[i])
279 return node
280
281
282 - def reinsert(self, node, index, bounds):
283 """
284 Inserts 'node' at the position it had in all lists in [0, 'index'[
285 before it was removed. This method assumes that the next and previous
286 nodes of the node that is reinserted are in the list.
287
288 """
289 for i in xrange(index):
290 node.prev[i].next[i] = node
291 node.next[i].prev[i] = node
292 if bounds[i] > node.cargo[i]:
293 bounds[i] = Ff(node.cargo[i])
294
295
296
297 if __name__ == "__main__":
298
299
300 referencePoint = [2, 2, 2]
301 hv = HyperVolume(referencePoint)
302 front = [[1,0,1], [0,1,0]]
303 volume = hv.compute(front)
304