1
2
3 """
4 This module contains the implementation of the Multiobjective framework called
5 Sofomore, and its instantiation with cma-es to obtain COMO-CMA-ES, defined in
6 the paper [Toure, Cheikh, et al. "Uncrowded Hypervolume Improvement:
7 COMO-CMA-ES and the Sofomore framework."
8 GECCO'19-Genetic and Evolutionary Computation Conference. 2019.].
9
10 Only the bi-objective framework is functional and has been thoroughly tested.
11 """
12
13 from __future__ import division, print_function, unicode_literals
14 __author__ = "Cheikh Toure and Nikolaus Hansen"
15 __license__ = "BSD 3-clause"
16 __version__ = "0.5.1"
17 del division, print_function, unicode_literals
18
19 import ast
20 import numpy as np
21 import cma
22 from cma import interfaces
23 from .nondominatedarchive import NonDominatedList
24 from moarchiving import BiobjectiveNondominatedSortedList
25 import warnings
26 import cma.utilities.utils
27 import os
28 from .sofomore_logger import SofomoreDataLogger
29
30
31 -class Sofomore(interfaces.OOOptimizer):
32 """
33 Sofomore framework for multiobjective optimization, with the
34 ask-and-tell interface.
35 See: [Toure, Cheikh, et al. "Uncrowded Hypervolume Improvement:
36 COMO-CMA-ES and the Sofomore framework."
37 GECCO'19-Genetic and Evolutionary Computation Conference. 2019.].
38
39 Calling Sequences
40 =================
41
42 - ``moes = Sofomore(list_of_solvers_instances, reference_point, opts)``
43
44 - ``moes = Sofomore(list_of_solvers_instances, reference_point)``
45
46 - ``moes = Sofomore(list_of_solvers_instances,
47 reference_point).optimize(objective_fcts)``
48
49 Arguments
50 =========
51 `list_of_solvers_instances`
52 list of instances of single-objective solvers.
53 It's generally created via a factory function.
54 Let's take the example of a factory function that returns cma-es
55 instances, called `get_cmas`. Then:
56 ``list_of_solvers_instances = get_cmas(11 * [x0], sigma0)``
57 creates a list of 11 cma-es instances of initial mean `x0` and initial
58 step-size `sigma0`.
59 A single-objective solver instance must have the following
60 attributes and methods:
61 - `incumbent`: an attribute that gives an estimate of
62 the single-objective solver.
63 - `objective_values`: an attribute that stores the objective
64 values of the incumbent.
65 - `stop`: a method returning a dictionary representing the
66 termination status.
67 - `ask`: generates new candidate solutions.
68 - `tell`: passes the objective values and updates the states of the
69 single-objective solvers.
70
71
72 `opts`
73 opts, a dictionary with optional settings related to the
74 Sofomore framework. It mainly contains the following keys:
75 - 'archive': default value is `True`.
76 If its value is `True`, tracks the non-dominated
77 points that dominate the reference point, among the points evaluated
78 so far during the optimization.
79 The archive will not interfere with the optimization
80 process.
81 - 'indicator_front': default value is `None`. Used via
82 `self.indicator_front = IndicatorFront(self.opts['indicator_front'])`
83 within the __init__ method of Sofomore. See the class IndicatorFront
84 for more details.
85 - 'restart': used to define in __init__ the attribute `restart` via
86 `self.restart = self.opts['restart']`.
87 - 'update_order': default value is a function that takes a natural
88 integer as input and returns a random number between 0 and 1.
89 It is used as a `key value` in: `sorted(..., key = ...)`, and guides the
90 order in which the kernels will be updated during the optimization.
91
92
93 `reference_point`
94 reference point of the multiobjective optimization.
95 Its default value is `None` but should be set by the user
96 beforehand to guarantee an optimal p-distribution convergence of
97 the Hypervolume indicator of `p` points towards the Pareto set/front.
98 It can be changed dynamically by the user if needed.
99
100
101 Main interface / usage
102 ======================
103 The interface is inherited from the generic `OOOptimizer`
104 class, which is the same interface used by the `pycma` module. An object
105 instance is generated as following::
106
107 >>> import cma, comocma
108 >>> import numpy as np
109 >>> reference_point = [11, 11]
110 >>> num_kernels = 11 # the number of points we seek to have on the Pareto front
111 >>> dimension = 10 # the dimension of the search space
112 >>> x0 = dimension * [0] # an initial mean for cma
113 >>> sigma0 = 0.2 # initial step-size for cma
114 >>> list_of_solvers_instances = comocma.get_cmas(num_kernels * [x0], sigma0, {'verbose':-9})
115 >>> # `comocma.get_cmas` is a factory function that returns `num_kernels` cma-es instances
116 >>> moes = comocma.Sofomore(list_of_solvers_instances,
117 ... reference_point) #instantiation of our MO optimizer
118
119 The least verbose interface is via the optimize method::
120 >>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1)) # a callable bi-objective function
121 >>> moes.optimize(fitness) # doctest:+ELLIPSIS
122 Iterat #Fevals Hypervolume axis ratios sigmas min&max stds***
123
124 More verbosely, the optimization of the callable multiobjective function
125 `fitness` is done via the `ask-and-tell` interface::
126
127 >>> moes = comocma.Sofomore(list_of_solvers_instances, reference_point)
128 >>> while not moes.stop() and moes.countiter < 30:
129 ... solutions = moes.ask() # `ask` delivers new candidate solutions
130 ... objective_values = [fitness(x) for x in solutions]
131 ... moes.tell(solutions, objective_values)
132 ... # `tell` updates the MO instance by passing the respective function values.
133 ... moes.disp() # display data on the evolution of the optimization
134
135 One iteration of the `optimize` interface is equivalent to one step in the
136 loop of the `ask-and-tell` interface. But for the latter, the prototyper has
137 more controls to analyse and guide the optimization, due to the access of
138 the instance between the `ask` and the `tell` calls.
139
140 Attributes and Properties
141 =========================
142
143 - `archive`: list of non-dominated points among all points evaluated
144 so far, which dominate the reference point.
145 - `countevals`: the number of function evaluations.
146 - `countiter`: the number of iterations.
147 - `dimension`: is the dimension of the search space.
148 - `indicator_front`: the indicator used as a changing fitness inside an
149 iteration of Sofomore. By default it is the UHVI of all the objective values
150 of the kernels' incumbents, except the kernel being optimized.
151 See the class `IndicatorFront` for more details.
152 - `isarchive`: a boolean accessible via `self.opts['archive']`. If `True`,
153 we keep track of the archive, otherwise we don't.
154 - `kernels`: initialized with `list_of_solvers_instances`,
155 and is the list of single-objective solvers.
156 - `key_sort_indices`: default value is `self.opts['update_order']`.
157 It is a function used as a key to sort some kernels' indices, in order to
158 select the first indices during the call of the `ask` method.
159 - `logger`: an attribute that accounts for the way we log the Sofomore data
160 during an optimization. `self.logger` is an instance of the SofomoreDataLogger
161 class.
162 - `NDA`: it is the non-dominated archiving method used within Sofomore.
163 By default the class `BiobjectiveNondominatedSortedList` is used
164 in two objectives and the class (non tested yet) `NonDominatedList` is used
165 for three or more objectives.
166 - `opts`: passed options.
167 - `offspring`: list of tuples of the index of a kernel with its
168 corresponding candidate solutions, that we generally get with the cma's
169 `ask` method.
170 - `pareto_front_cut`: list of non-dominated points among the incumbents of
171 `self.kernels`, which dominate the reference point.
172 - `pareto_set_cut`: preimage of `pareto_front_cut`.
173 - `reference_point`: the current reference point.
174 - `restart`: accessible via `self.opts['restart']`. If not `None`, a callback
175 function that adds kernels after a kernel of the running Sofomore instance
176 stops. Its default value is `None`, meaning that we do not do any restart.
177 - `_active_indices`: the indices of the kernels that have not stopped yet.
178 - `_last_stopped_kernel_id`: the index of the last stopped kernel.
179 - `_told_indices`: the kernels' indices for which we will evaluate the
180 objective values of their incumbents, before the next call of the `tell`
181 method. And before the first call of `tell`, they are the indices of all the
182 initial kernels (i.e. `range(len(self))`). And before another call of
183 `tell`, they are the indices of the kernels from which we have sampled new
184 candidate solutions during the penultimate `ask` method.
185 Note that we should call the `ask` method before any call of the `tell`
186 method.
187
188 """
189 - def __init__(self,
190 list_of_solvers_instances,
191
192 reference_point=None,
193 opts=None,
194 ):
195 """
196 Initialization:
197 - `list_of_solvers_instances` is a list of single-objective
198 solvers' instances
199 - The reference_point is set by the user during the
200 initialization.
201 - `opts` is a dictionary updating the values of 'indicator_front',
202 'archive', 'restart', 'update_order'; that respond respectfully to
203 the changing fitness we will choose within an iteration of Sofomore,
204 whether or not keeping an archive, how to do the restart in case of
205 any restart, and the order of update of the kernels. It also has
206 the keys 'verb_filename', 'verb_log' and 'verb_disp'; that
207 respectfully indicate the name of the filename containing the Sofomore
208 data, the logging of the Sofomore data every 'verb_log' iterations
209 and the display of the data via `self.disp()` every 'verb_disp'
210 iterations.
211 """
212 assert len(list_of_solvers_instances) > 0
213 self.kernels = list_of_solvers_instances
214 self.dimension = self.kernels[0].N
215 self._active_indices = list(range(len(self)))
216
217 for kernel in self.kernels:
218 if not hasattr(kernel, 'objective_values'):
219 kernel.objective_values = None
220 self.reference_point = reference_point
221 defopts = {'archive': True, 'restart': None, 'verb_filename': 'outsofomore' + os.sep,
222 'verb_log': 1, 'verb_disp': 100, 'update_order': sort_random,
223 'indicator_front': None
224 }
225 if opts is None:
226 opts = {}
227 if isinstance(opts, dict):
228 unvalid_keys = [k for k in opts.keys() if k not in defopts.keys()]
229 if len(unvalid_keys) > 0:
230 unvalid_key_not_str = [k for k in unvalid_keys if not isinstance(k, str)]
231 if len(unvalid_key_not_str) > 0:
232 raise ValueError("All the options's keys must be of TYPE str")
233 unvalid_keys_str = ', '.join(unvalid_keys)
234 raise ValueError('The following keys of opts are not valid: ' + unvalid_keys_str + '.')
235
236 defopts.update(opts)
237 else:
238 warnings.warn("options should be either a dictionary or None.")
239 self.opts = defopts
240 self.restart = self.opts['restart']
241 self.isarchive = self.opts['archive']
242 if self.isarchive:
243 self.archive = []
244 self.NDA = None
245 self.indicator_front = IndicatorFront(self.opts['indicator_front'])
246 self.offspring = []
247 self._told_indices = range(len(self))
248
249 self.key_sort_indices = self.opts['update_order']
250 self.countiter = 0
251 self.countevals = 0
252 self._remaining_indices_to_ask = range(len(self))
253 self.logger = SofomoreDataLogger(self.opts['verb_filename'],
254 modulo=self.opts['verb_log']).register(self)
255 self.best_hypervolume_pareto_front = 0.0
256 self.epsilon_hypervolume_pareto_front = 0.1
257 self._ratio_nondom_offspring_incumbent = len(self) * [0]
258
259 self._last_stopped_kernel_id = None
260
262 """
263 make `self` iterable.
264 """
265 return iter(self.kernels)
266
268 """
269 make `self` subscriptable.
270 """
271 return self.kernels[i]
272
274 """return length of the `Sofomore` instance by calling ``len(.)``.
275
276 The length is the number of (active and inactive) kernels
277 and hence consistent with subscription like
278 ``[moes[i] for i in range(len(moes)) if i in moes._active_indices]``.
279 """
280 return len(self.kernels)
281
283 """return archive for uncrowded hypervolume improvement indicator for `kernel`.
284
285 `kernel` can also be the respective index in `self`.
286 """
287
288
289 try:
290 kernel = self[kernel]
291 except TypeError:
292 pass
293 return self.NDA([k.objective_values for k in self
294 if k != kernel and k.objective_values is not None],
295 self.reference_point)
296
298 """return indicator function(!) for uncrowded hypervolume improvement for `kernel`.
299
300 >>> import comocma, cma
301 >>> list_of_solvers_instances = comocma.get_cmas(13 * [5 * [1]], 0.7, {'verbose':-9})
302 >>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1))
303 >>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
304 >>> moes.optimize(fitness, iterations=37) # doctest:+ELLIPSIS
305 Iterat #Fevals Hypervolume axis ratios sigmas min&max stds***
306 >>> moes._UHVI_indicator(moes[1])(moes[2].objective_values) # doctest:+ELLIPSIS
307 ***
308 >>> moes._UHVI_indicator(1)(moes[2].objective_values) # doctest:+ELLIPSIS
309 ***
310
311 both return the UHVI indicator function for kernel 1 and evaluate
312 kernel 2 on it::
313
314 >>> [[moes._UHVI_indicator(k)(k.objective_values)] for k in moes] # doctest:+ELLIPSIS
315 ***
316
317 is the list of UHVI values for all kernels where kernels occupying the
318 very same objective value have indicator value zero.
319 """
320 return self._UHVI_indicator_archive(kernel).hypervolume_improvement
321
322 - def sorted(self, key=None, reverse=True, **kwargs):
323 """return a reversed sorted list of kernels.
324
325 By default kernels are reversed sorted by HV contribution or UHVI
326 (which we aim to maximize) in the set of kernels. Exact copies have
327 zero or negative UHVI value.
328
329 >>> import comocma, cma
330 >>> list_of_solvers_instances = comocma.get_cmas(13 * [5 * [1]], 0.7, {'verbose':-9})
331 >>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1))
332 >>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
333 >>> moes.optimize(fitness, iterations=31) # doctest:+ELLIPSIS
334 Iterat #Fevals Hypervolume axis ratios sigmas min&max stds***
335 >>> moes.sorted(key = lambda k: moes.archive.contributing_hypervolume(
336 ... k.objective_values)) # doctest:+ELLIPSIS
337 [<comocma.como.CmaKernel object at***
338
339 sorts w.r.t. archive contribution (clones may get positive contribution).
340
341 """
342 def hv_improvement(kernel):
343 if kernel.objective_values is None:
344 return float('-inf')
345 return self._UHVI_indicator(kernel)(kernel.objective_values)
346 if key is None:
347 key = hv_improvement
348 return sorted(self, key=key, reverse=reverse, **kwargs)
349
350 - def ask(self, number_to_ask=1):
351 """
352 get the kernels' incumbents to be evaluated and sample new candidate solutions from
353 `number_to_ask` kernels.
354 The sampling is done by calling the `ask` method of the
355 `cma.CMAEvolutionStrategy` class.
356 The indices of the considered kernels' incumbents are given by the
357 `_told_indices` attribute.
358
359 To get the `number_to_ask` kernels, we use the function `self.key_sort_indices` as
360 a key to sort `self._remaining_indices_to_ask` (which is the list of
361 kernels' indices wherein we choose the first `number_to_ask` elements.
362 And if `number_to_ask` is larger than `len(self._remaining_indices_to_ask)`,
363 we select the list `self._remaining_indices_to_ask` extended with the
364 first `number_to_ask - len(self._remaining_indices_to_ask)` elements
365 of `range(len(self))`, sorted with `self.key_sort_indices` as key.
366
367 Arguments
368 ---------
369 - `number_to_ask`: the number of kernels for which we sample solutions
370 from, it is of TYPE int or str and is smaller or equal to `len(self)`.
371 The unique case where it is a str instance is when it is equal to
372 "all", meaning that all the kernels are asked. That case is mainly used
373 on parallel mode by distributing the evaluations of the kernels at the
374 same time before calling the `tell` method. The opposite is when
375 `number_to_ask` is equal to 1, which is the exact COMO-CMA-ES algorithm,
376 where the evaluations are done sequentially by updating the kernels one
377 by one.
378
379 Return
380 ------
381 The list of the kernels' incumbents to be evaluated, extended with a
382 list of N-dimensional (N is the dimension of the search space)
383 candidate solutions generated from `number_to_ask` kernels
384 to be evaluated.
385
386 :See: the `ask` method from the class `cma.CMAEvolutionStrategy`,
387 in `evolution_strategy.py` from the `cma` module.
388
389 """
390
391 if number_to_ask == "all":
392 number_to_ask = len(self._active_indices)
393 assert number_to_ask > 0
394 if number_to_ask > len(self._active_indices):
395 number_to_ask = len(self._active_indices)
396 warnings.warn("value larger than the number of active kernels {}. ".format(
397 len(self._active_indices)) + "Set to {}.".format(len(self._active_indices)))
398 self.offspring = []
399 res = [self.kernels[i].incumbent for i in self._told_indices]
400 indices_to_ask = self._indices_to_ask(number_to_ask)
401 for ikernel in indices_to_ask:
402 kernel = self.kernels[ikernel]
403 offspring = kernel.ask()
404 res.extend(offspring)
405 self.offspring += [(ikernel, offspring)]
406
407 return res
408
409 - def tell(self, solutions, objective_values):
410 """
411 pass objective function values to update the state variables of some
412 kernels, `self._told_indices` and eventually `self.archive`.
413 Arguments
414 ---------
415 `solutions`
416 list or array of points (of type `numpy.ndarray`), most presumably
417 before delivered by the `ask()` method.
418 `objective_values`
419 list of multiobjective function values (of type `list`)
420 corresponding to the respective points in `solutions`.
421 `constraints_values`
422 list of list of constraint values: each element is a list containing
423 the values of one constraint function, that are obtained by evaluation
424 on `solutions`.
425
426
427 Details
428 -------
429 To update a kernel, `tell()` applies the kernel's `tell` method
430 to the kernel's corresponding candidate solutions (offspring) along
431 with the "changing" fitness `- self.indicator_front.hypervolume_improvement`.
432
433 :See:
434 - the `tell` method from the class `cma.CMAEvolutionStrategy`,
435 in `evolution_strategy.py` from the `cma` module.
436 - the `hypervolume_improvement` method from the class
437 `BiobjectiveNondominatedSortedList`, in the module `moarchiving.py`
438 - the `hypervolume_improvement` method from the class
439 `NonDominatedList`, in the module `nondominatedarchive.py`
440 """
441 if len(solutions) == 0:
442 return
443 if self.NDA is None:
444 self.NDA = BiobjectiveNondominatedSortedList if len(
445 objective_values[0]) == 2 else NonDominatedList
446
447 objective_values = np.asarray(objective_values).tolist()
448
449 for i in range(len(self._told_indices)):
450 self.kernels[self._told_indices[i]].objective_values = objective_values[i]
451
452 if self.reference_point is None:
453 pass
454
455 start = len(self._told_indices)
456 self._told_indices = []
457 for ikernel, offspring in self.offspring:
458 self.indicator_front.set_kernel(self[ikernel], self)
459 hypervolume_improvements = [self.indicator_front.hypervolume_improvement(point)
460 for point in objective_values[start:start+len(offspring)]]
461 kernel = self.kernels[ikernel]
462 if kernel.fit.median0 is not None and kernel.fit.median0 >= 0:
463
464
465
466
467
468
469
470 kernel.fit.median0 = None
471 kernel.tell(offspring, [-float(u) for u in hypervolume_improvements])
472
473
474 if kernel.stop():
475 self._active_indices.remove(ikernel)
476 self._last_stopped_kernel_id = ikernel
477
478 if self.restart is not None:
479 kernel_to_add = self.restart(self)
480 self._told_indices += [len(self)]
481 self.add(kernel_to_add)
482
483 try:
484 kernel.logger.add()
485 except:
486 pass
487 kernel._last_offspring_f_values = objective_values[start:start+len(offspring)]
488
489 start += len(offspring)
490
491 self._told_indices += [u for (u,v) in self.offspring]
492
493 current_hypervolume = self.pareto_front_cut.hypervolume
494 epsilon = abs(current_hypervolume - self.best_hypervolume_pareto_front)
495 if epsilon:
496 self.epsilon_hypervolume_pareto_front = min(self.epsilon_hypervolume_pareto_front,
497 epsilon)
498 self.best_hypervolume_pareto_front = max(self.best_hypervolume_pareto_front,
499 current_hypervolume)
500
501 if self.isarchive:
502 if not self.archive:
503 self.archive = self.NDA(objective_values, self.reference_point)
504 else:
505 self.archive.add_list(objective_values)
506 self.countiter += 1
507 self.countevals += len(objective_values)
508
509
510 @property
512 """
513 return the non-dominated solutions dominating the reference point,
514 among the kernels' objective values.
515 It's the image of `self.pareto_set_cut`.
516 """
517 return self.NDA([kernel.objective_values for kernel in self.kernels \
518 if kernel.objective_values is not None],
519 self.reference_point)
520
521 @property
523 """
524 return the non-dominated solutions whose images dominate the
525 reference point, among the kernels' incumbents.
526 It's the pre-image of `self.pareto_front_cut`.
527 """
528 return [kernel.incumbent for kernel in self.kernels if \
529 kernel.objective_values in self.pareto_front_cut]
530
531 @property
533 """
534 provisorial, return _all_ non-dominated objective values irrespectively
535
536 of the current reference point. Only points whose contributing HV
537 does not depend on the current reference point still have the same
538 cHV in the resulting `list`. HV improvements in general may change.
539 """
540 f_pairs = [k.objective_values for k in self
541 if k.objective_values is not None]
542
543 def reference(f_pairs):
544 reference = []
545 for i in [0, 1]:
546 reference[i] = max(f[i] for f in f_pairs)
547 reference[i] = 1.1**np.sign(reference[i]) * reference[i] + 1
548 return reference
549 if not f_pairs:
550 if self.reference_point in (None, (), [], {}):
551 warnings.warn("Sofomore.pareto_front_uncut: self.reference_point = %s"
552 " (#kernels=%d) which should never happen"
553 % (str(self.reference_point), len(self)))
554 return []
555 return self.NDA(f_pairs, self.reference_point)
556 return self.NDA(f_pairs, reference(f_pairs))
557
559 """
560 return a nonempty dictionary when all kernels stop, containing all the
561 termination status. Therefore it's solely ... on the kernels' `stop`
562 method, which also return dictionaries.
563 Return
564 ------
565 For example with 5 kernels, stop should return either None, or a `dict`
566 of the form:
567 {0: dict0,
568 1: dict1,
569 2: dict2,
570 3: dict2,
571 4: dict4},
572 where each index `i` is a key which value is the `dict` instance
573 `self.kernels[i].stop()`
574 """
575 res = {}
576 for i in range(len(self)):
577 if self.kernels[i].stop():
578 res[i] = self.kernels[i].stop()
579 else:
580 return False
581 return res
582
583 @property
585 """
586 return a dictionary of the current termination states of the kernels.
587
588 """
589 res = {}
590 for i in range(len(self)):
591 res[i] = self.kernels[i].stop()
592 return res
593
594 - def add(self, kernels):
595 """
596 add `kernels` of type `list` to `self.kernels`.
597 Generally, `kernels` are created from a factory function.
598 If `kernels` is of length 1, the brackets can be omitted.
599 """
600 if not isinstance(kernels, list):
601 kernels = [kernels]
602 self.kernels += kernels
603
604 self._active_indices = [idx for idx in range(len(self)) if \
605 not self.kernels[idx].stop()]
606 self._ratio_nondom_offspring_incumbent = len(self) * [0]
607
609 """
610 remove elements of the `kernels` (type `list`) that belong to
611 `self.kernels`, and update the `_active_indices` attribute.
612 If `kernels` is of length 1, the brackets can be omitted.
613 """
614 if not isinstance(kernels, list):
615 kernels = [kernels]
616 for kernel in kernels:
617 if kernel in self.kernels:
618 self.kernels.remove(kernel)
619
620 self._active_indices = [idx for idx in range(len(self)) if \
621 not self.kernels[idx].stop()]
622
623 @property
640
641 @property
643 """
644 """
645 res = 0.0
646 for kernel in self.kernels:
647 conv = np.sqrt(kernel.dC)
648 vec = []
649 for i in range(len(kernel.pc)):
650 xi = max(kernel.sigma_vec*kernel.pc[i], kernel.sigma_vec*conv[i])
651 vec += [kernel.sigma * xi / kernel.sigma0]
652 res = max(res, max(vec))
653 return res
654
656 """
657 """
658 sorted_indices = sorted(self._remaining_indices_to_ask, key = self.key_sort_indices)
659 indices_to_ask = []
660 remaining_indices = []
661 if number_to_ask <= len(sorted_indices):
662 indices_to_ask = sorted_indices[:number_to_ask]
663 remaining_indices = sorted_indices[number_to_ask:]
664 else:
665 val = number_to_ask - len(sorted_indices)
666 indices_to_ask = sorted_indices
667 sorted_indices = sorted(self._active_indices, key = self.key_sort_indices)
668 indices_to_ask += sorted_indices[:val]
669 remaining_indices = sorted_indices[val:]
670
671 self._remaining_indices_to_ask = remaining_indices
672 return indices_to_ask
673
675 """
676 inactivate `kernel`, assuming that it's an element of `self.kernels`,
677 or an index in `range(len(self))`.
678 When inactivated, `kernel` is no longer updated, it is ignored.
679 However we do not remove it from `self.kernels`, meaning that `kernel`
680 might still play a role, due to its eventual trace in `self.pareto_front_cut`.
681
682 """
683 if kernel in self.kernels:
684 ikernel = self.kernels.index(kernel)
685
686 try:
687 self._active_indices.remove(ikernel)
688 self.kernels[ikernel].opts['termination_callback'] += (lambda _: 'kernel turned off',)
689 except (AttributeError, TypeError, KeyError, ValueError):
690 warnings.warn("check again if `opts['termination_callback']` is"+
691 " correctly used, or if the kernel is not already"+
692 " turned off.")
693
695 """
696 activate `kernel` when it was inactivated beforehand. Otherwise
697 it remains quiet.
698
699 We expect the kernel's `stop` method in interest to look like:
700 kernel.stop() = {'callback': ['kernel turned off']}
701 """
702 raise NotImplementedError
703 if kernel in self.kernels:
704 ikernel = self.kernels.index(kernel)
705 new_list = [callback for callback in self.kernels[ikernel].opts['termination_callback']\
706 if callback(kernel) == 'kernel turned off']
707 kernel.opts['termination_callback'] = new_list
708 if not kernel.stop():
709 self._active_indices += [ikernel]
710
711
712
714 """
715 copy-pasted from `cma.evolution_strategy`.
716 print annotation line for `disp` ()"""
717 self.has_been_called = True
718 print('Iterat #Fevals Hypervolume axis ratios '
719 ' sigmas min&max stds\n'+'(median)'.rjust(42) +
720 '(median)'.rjust(10) + '(median)'.rjust(12))
721
722 - def disp(self, modulo=None):
723 """
724 copy-pasted from `cma.evolution_strategy`.
725 print current state variables in a single-line.
726 copy-pasted from `cma.evolution_strategy` module
727
728 Prints only if ``iteration_counter % modulo == 0``.
729
730 :See also: `disp_annotation`.
731 """
732 if modulo is None:
733 modulo = self.opts['verb_disp']
734
735
736
737 if modulo:
738 if not hasattr(self, 'has_been_called'):
739 self.disp_annotation()
740
741 if self.countiter > 0 and (self.stop() or self.countiter < 4
742 or self.countiter % modulo < 1):
743 try:
744 print(' '.join((repr(self.countiter).rjust(5),
745 repr(self.countevals).rjust(6),
746 '%.15e' % (self.pareto_front_cut.hypervolume),
747 '%4.1e' % (np.median([kernel.D.max() / kernel.D.min()
748 if not kernel.opts['CMA_diagonal'] or kernel.countiter > kernel.opts['CMA_diagonal']
749 else max(kernel.sigma_vec*1) / min(kernel.sigma_vec*1) \
750 for kernel in self.kernels])),
751 '%6.2e' % (np.median([kernel.sigma for kernel in self.kernels])),
752 '%6.0e' % (np.median([kernel.sigma * min(kernel.sigma_vec * kernel.dC**0.5) \
753 for kernel in self.kernels])),
754 '%6.0e' % (np.median([kernel.sigma * max(kernel.sigma_vec * kernel.dC**0.5) \
755 for kernel in self.kernels]))
756 )))
757 except AttributeError:
758 pass
759
760
761
762
763
764
765
766 return self
767
769 """with `hypervolume_improvement` method based on a varying empirical front.
770
771 The front is either all kernels but one or based on the
772 `list_attribute` of `moes` (like `archive`) as given on
773 initialization.
774
775 Usage::
776 >>> import comocma, cma
777 >>> list_of_solvers_instances = comocma.get_cmas(13 * [5 * [0.4]], 0.7, {'verbose':-9})
778 >>> fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1))
779 >>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
780 >>> moes.front_observed = IndicatorFront()
781 >>> moes.optimize(fitness, iterations=47) # doctest:+ELLIPSIS
782 Iterat #Fevals Hypervolume axis ratios sigmas min&max stds***
783 >>> moes = comocma.Sofomore(list_of_solvers_instances, [11, 11])
784 >>> moes.front_observed = IndicatorFront(list_attribute='archive')
785 >>> moes.optimize(fitness, iterations=37) # doctest:+ELLIPSIS
786 Iterat #Fevals Hypervolume axis ratios sigmas min&max stds***
787 >>> moes.front_observed.set_kernel(moes[3], moes)
788 >>> f_points = [moes.front_observed.hypervolume_improvement(point)
789 ... for point in moes[3]._last_offspring_f_values]
790
791 """
792 - def __init__(self, list_attribute=None, NDA=None):
793 """``getattr(moes, list_attribute)`` contains the list to create the front.
794
795 `NDA` is a non-dominated archive with a `hypervolume_improvement` method.
796
797 """
798 self.list_attribute = list_attribute
799 self.NDA = NDA or BiobjectiveNondominatedSortedList
800 self.kernel = None
801 self.front = None
802
805
807 """Set empirical front for evolving the given kernel.
808
809 By default, make changes only when kernel has changed.
810
811 Details: ``moes.reference_point`` and, in case, its attribute
812 with name `self.list_attribute: str` is used.
813 """
814 if lazy and kernel == self.kernel:
815 return
816 if self.list_attribute:
817 self.front = self.NDA(getattr(moes, self.list_attribute),
818 moes.reference_point)
819 else:
820 self.front = self.NDA([k.objective_values for k in moes if k != kernel],
821 moes.reference_point)
822 self.kernel = kernel
823
824 cma_kernel_default_options_replacements = {
825 'conditioncov_alleviate': [np.inf, np.inf],
826 'verbose': -1,
827 'tolx': 1e-4,
828 'tolfunrel': 0,
829 'tolfun': 0,
830 'tolfunhist': 0,
831 'tolstagnation': 1e23,
832 }
833
834 -def get_cmas(x_starts, sigma_starts, inopts=None, number_created_kernels=0):
835 """
836 Factory function that produces `len(x_starts)` instances of type `cmaKernel`.
837
838 Parameters
839 ----------
840 x_starts : TYPE list or list of lists or list of or ndarrays or ndarray
841 The initial means of the returned cmas.
842 sigma_starts : TYPE float or list of floats
843 The initial step-sizes of the returned cmas.
844 inopts : TYPE dict or list of dicts, optional
845 The cmas' options.
846 number_created_kernels : TYPE int, optional
847 Used as the starting index for the returned cma's names.
848 The values of the options' key 'verb_filenameprefix' rely upon this
849 argument `number_created_kernels`.
850
851 Returns
852 -------
853 A list of `CmaKernel` instances.
854
855 Example::
856 >>> import comocma
857 >>> dimension = 10
858 >>> sigma0 = 0.5
859 >>> num_kernels = 11
860 >>> cma_opts = {'tolx': 10**-4, 'popsize': 32}
861 >>> list_of_solvers = comocma.get_cmas(num_kernels * [dimension * [0]], sigma0, cma_opts)
862
863 produce `num_kernels` cma instances.
864 """
865
866 if x_starts is not None and len(x_starts):
867 try:
868 x_starts = x_starts.tolist()
869 except:
870 pass
871 try:
872 x_starts = [u.tolist() for u in x_starts]
873 except:
874 pass
875 if not isinstance(x_starts[0], list):
876 x_starts = [x_starts]
877
878 kernels = []
879 num_kernels = len(x_starts)
880 if not isinstance(sigma_starts, list):
881 sigma_starts = num_kernels * [sigma_starts]
882 if inopts is None:
883 inopts = {}
884 list_of_opts = []
885 if isinstance(inopts, list):
886 list_of_opts = inopts
887 else:
888 list_of_opts = [dict(inopts) for _ in range(num_kernels)]
889
890
891 for i in range(len(x_starts)):
892 try:
893 mybounds = list_of_opts[i]['bounds']
894 if isinstance(mybounds, str):
895 mybounds = ast.literal_eval(mybounds)
896 bounds_transform = cma.constraints_handler.BoundTransform(mybounds)
897 x_starts[i] = bounds_transform.repair(x_starts[i])
898 except KeyError:
899 pass
900
901 for i in range(num_kernels):
902 defopts = cma.CMAOptions()
903 defopts.update(cma_kernel_default_options_replacements)
904 if isinstance(list_of_opts[i], dict):
905 defopts.update(list_of_opts[i])
906 defopts.update({'verb_filenameprefix': 'cma_kernels' + os.sep +
907 str(number_created_kernels+i)})
908
909 kernels += [CmaKernel(x_starts[i], sigma_starts[i], defopts)]
910
911 return kernels
912
914 """
915 inheriting from the `cma.CMAEvolutionStrategy` class, by adding the property
916 `incumbent`, the attributes `objective_values` and `_last_offspring_f_values`.
917 """
918 - def __init__(self, x0, sigma0, inopts=None):
919 """
920 Arguments
921 =========
922 `x0`
923 initial solution, starting point. `x0` is given as "phenotype"
924 which means, if::
925
926 opts = {'transformation': [transform, inverse]}
927
928 is given and ``inverse is None``, the initial mean is not
929 consistent with `x0` in that ``transform(mean)`` does not
930 equal to `x0` unless ``transform(mean)`` equals ``mean``.
931 `sigma0`
932 initial standard deviation. The problem variables should
933 have been scaled, such that a single standard deviation
934 on all variables is useful and the optimum is expected to
935 lie within about `x0` +- ``3*sigma0``. See also options
936 `scaling_of_variables`. Often one wants to check for
937 solutions close to the initial point. This allows,
938 for example, for an easier check of consistency of the
939 objective function and its interfacing with the optimizer.
940 In this case, a much smaller `sigma0` is advisable.
941 `inopts`
942 options, a dictionary with optional settings,
943 see class `cma.CMAOptions`.
944 """
945 cma.CMAEvolutionStrategy.__init__(self, x0, sigma0, inopts)
946 self.objective_values = None
947
948 self._last_offspring_f_values = None
949
950
951 @property
953 """
954 it gives the 'repaired' mean of a cma-es. For a problem with bound
955 constraints, `self.incumbent` in inside the bounds.
956 """
957 return self.boundary_handler.repair(self.mean)
958
960 """tentative copy of self, versatile (interface and functionalities may change).
961
962 This may not work depending on the used sampler.
963
964 Copy mean and sample distribution parameters and input options.
965
966 Do not copy evolution paths, termination status or other state variables.
967 """
968 es = super(CmaKernel, self)._copy_light(sigma, inopts)
969
970 es.objective_values = self.objective_values
971 es._last_offspring_f_values = self._last_offspring_f_values
972 return es
973
975 """
976 Define a callable multiobjective function from single objective ones.
977 Example:
978 fitness = comocma.FitFun(cma.ff.sphere, lambda x: cma.ff.sphere(x-1)).
979 """
981 self.callables = args
983 return [f(x) for f in self.callables]
984
991 """
992 Used for the update order of a Sofomore instance.
993 Example::
994 moes = Sofomore(list_of_instances, reference_point, {'update_order': sort_random})
995 randomly picks the kernels to update in the `tell` method of Sofomore.
996
997 """
998 return np.random.rand()
999
1001 """
1002 Example::
1003 moes = Sofomore(list_of_instances, reference_point, {'update_order': sort_increasing})
1004 updates respectively `self[0]`, `self[1]`, ..., `self[-1]`
1005 in the `tell` method of Sofomore.
1006 """
1007 return i
1008
1010 """
1011 Example::
1012 moes = Sofomore(list_of_instances, reference_point, {'update_order': sort_decreasing})
1013 updates respectively `self[-1]`, `self[-2]`, ..., `self[0]`
1014 in the `tell` method of Sofomore.
1015 """
1016 return -i
1017
1019 """
1020 Example::
1021 moes = Sofomore(list_of_instances, reference_point, {'update_order': sort_even_odds})
1022 pick the kernels with even indices before the kernels with odd indices in
1023 the `tell` method of Sofomore.
1024 """
1025 return i % 2
1026
1028 """
1029 Example::
1030 moes = Sofomore(list_of_instances, reference_point, {'update_order': sort_odds_even})
1031 pick the kernels with odd indices before the kernels with even indices
1032 in the `tell` method of Sofomore.
1033 """
1034 return - (i % 2)
1035