Completed
Push — master ( e51968...a7558a )
by
unknown
13s queued 11s
created

NiaPy.task.task.Task.__init__()   C

Complexity

Conditions 10

Size

Total Lines 47
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 18
nop 8
dl 0
loc 47
rs 5.9999
c 0
b 0
f 0

How to fix   Complexity    Many Parameters   

Complexity

Complex classes like NiaPy.task.task.Task.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
# encoding=utf8
2
3
"""The implementation of tasks."""
4
5
import logging
6
from enum import Enum
7
8
from matplotlib import pyplot as plt
9
from numpy import inf, random as rand
10
11
from NiaPy.util import (
12
    limit_repair,
13
    fullArray,
14
    FesException,
15
    GenException,
16
    RefException
17
)
18
from NiaPy.benchmarks.utility import Utility
19
20
21
logging.basicConfig()
22
logger = logging.getLogger("NiaPy.task.Task")
23
logger.setLevel("INFO")
24
25
26
class OptimizationType(Enum):
27
    r"""Enum representing type of optimization.
28
29
    Attributes:
30
            MINIMIZATION (int): Represents minimization problems and is default optimization type of all algorithms.
31
            MAXIMIZATION (int): Represents maximization problems.
32
33
    """
34
35
    MINIMIZATION = 1.0
36
    MAXIMIZATION = -1.0
37
38
39
class Task:
40
    r"""Class representing problem to solve with optimization.
41
42
    Date:
43
            2019
44
45
    Author:
46
            Klemen Berkovič and others
47
48
    Attributes:
49
            D (int): Dimension of the problem.
50
            Lower (numpy.ndarray): Lower bounds of the problem.
51
            Upper (numpy.ndarray): Upper bounds of the problem.
52
            bRange (numpy.ndarray): Search range between upper and lower limits.
53
            optType (OptimizationType): Optimization type to use.
54
55
    See Also:
56
            * :class:`NiaPy.util.Utility`
57
58
    """
59
60
    D = 0
61
    benchmark = None
62
    Lower, Upper, bRange = inf, inf, inf
63
    optType = OptimizationType.MINIMIZATION
64
65
    def __init__(self, D=0, optType=OptimizationType.MINIMIZATION, benchmark=None, Lower=None, Upper=None, frepair=limit_repair, **kwargs):
66
        r"""Initialize task class for optimization.
67
68
        Arguments:
69
                D (Optional[int]): Number of dimensions.
70
                optType (Optional[OptimizationType]): Set the type of optimization.
71
                benchmark (Union[str, Benchmark]): Problem to solve with optimization.
72
                Lower (Optional[numpy.ndarray]): Lower limits of the problem.
73
                Upper (Optional[numpy.ndarray]): Upper limits of the problem.
74
                frepair (Optional[Callable[[numpy.ndarray, numpy.ndarray, numpy.ndarray, Dict[str, Any]], numpy.ndarray]]): Function for reparing individuals components to desired limits.
75
76
        See Also:
77
                * `func`:NiaPy.util.Utility.__init__`
78
                * `func`:NiaPy.util.Utility.repair`
79
80
        """
81
82
        # dimension of the problem
83
        self.D = D
84
        # set optimization type
85
        self.optType = optType
86
        # set optimization function
87
        self.benchmark = Utility().get_benchmark(benchmark) if benchmark is not None else None
88
89
        if self.benchmark is not None:
90
            self.Fun = self.benchmark.function() if self.benchmark is not None else None
91
92
        # set Lower limits
93
        if Lower is not None:
94
            self.Lower = fullArray(Lower, self.D)
95
        elif Lower is None and benchmark is not None:
96
            self.Lower = fullArray(self.benchmark.Lower, self.D)
97
        else:
98
            self.Lower = fullArray(0, self.D)
99
100
        # set Upper limits
101
        if Upper is not None:
102
            self.Upper = fullArray(Upper, self.D)
103
        elif Upper is None and benchmark is not None:
104
            self.Upper = fullArray(self.benchmark.Upper, self.D)
105
        else:
106
            self.Upper = fullArray(0, self.D)
107
108
        # set range
109
        self.bRange = self.Upper - self.Lower
110
        # set repair function
111
        self.frepair = frepair
112
113
    def dim(self):
114
        r"""Get the number of dimensions.
115
116
        Returns:
117
                int: Dimension of problem optimizing.
118
119
        """
120
121
        return self.D
122
123
    def bcLower(self):
124
        r"""Get the array of lower bound constraint.
125
126
        Returns:
127
                numpy.ndarray: Lower bound.
128
129
        """
130
131
        return self.Lower
132
133
    def bcUpper(self):
134
        r"""Get the array of upper bound constraint.
135
136
        Returns:
137
                numpy.ndarray: Upper bound.
138
139
        """
140
141
        return self.Upper
142
143
    def bcRange(self):
144
        r"""Get the range of bound constraint.
145
146
        Returns:
147
                numpy.ndarray: Range between lower and upper bound.
148
149
        """
150
151
        return self.Upper - self.Lower
152
153
    def repair(self, x, rnd=rand):
154
        r"""Repair solution and put the solution in the random position inside of the bounds of problem.
155
156
        Arguments:
157
                x (numpy.ndarray): Solution to check and repair if needed.
158
                rnd (mtrand.RandomState): Random number generator.
159
160
        Returns:
161
                numpy.ndarray: Fixed solution.
162
163
        See Also:
164
                * :func:`NiaPy.util.limitRepair`
165
                * :func:`NiaPy.util.limitInversRepair`
166
                * :func:`NiaPy.util.wangRepair`
167
                * :func:`NiaPy.util.randRepair`
168
                * :func:`NiaPy.util.reflectRepair`
169
170
        """
171
172
        return self.frepair(x, self.Lower, self.Upper, rnd=rnd)
173
174
    def nextIter(self):
175
        r"""Increments the number of algorithm iterations."""
176
177
    def start(self):
178
        r"""Start stopwatch."""
179
180
    def eval(self, A):
181
        r"""Evaluate the solution A.
182
183
        Arguments:
184
                A (numpy.ndarray): Solution to evaluate.
185
186
        Returns:
187
                float: Fitness/function values of solution.
188
189
        """
190
191
        return self.Fun(self.D, A) * self.optType.value
192
193
    def isFeasible(self, A):
194
        r"""Check if the solution is feasible.
195
196
        Arguments:
197
                A (Union[numpy.ndarray, Individual]): Solution to check for feasibility.
198
199
        Returns:
200
                bool: `True` if solution is in feasible space else `False`.
201
202
        """
203
204
        return False not in (A >= self.Lower) and False not in (A <= self.Upper)
205
206
    def stopCond(self):
207
        r"""Check if optimization task should stop.
208
209
        Returns:
210
                bool: `True` if stopping condition is meet else `False`.
211
212
        """
213
214
        return False
215
216
217
class CountingTask(Task):
218
    r"""Optimization task with added counting of function evaluations and algorithm iterations/generations.
219
220
    Attributes:
221
            Iters (int): Number of algorithm iterations/generations.
222
            Evals (int): Number of function evaluations.
223
224
    See Also:
225
            * :class:`NiaPy.util.Task`
226
227
    """
228
229
    def __init__(self, **kwargs):
230
        r"""Initialize counting task.
231
232
        Args:
233
                **kwargs (Dict[str, Any]): Additional arguments.
234
235
        See Also:
236
                * :func:`NiaPy.util.Task.__init__`
237
238
        """
239
240
        Task.__init__(self, **kwargs)
241
        self.Iters, self.Evals = 0, 0
242
243
    def eval(self, A):
244
        r"""Evaluate the solution A.
245
246
        This function increments function evaluation counter `self.Evals`.
247
248
        Arguments:
249
                A (numpy.ndarray): Solutions to evaluate.
250
251
        Returns:
252
                float: Fitness/function values of solution.
253
254
        See Also:
255
                * :func:`NiaPy.util.Task.eval`
256
257
        """
258
259
        r = Task.eval(self, A)
260
        self.Evals += 1
261
        return r
262
263
    def evals(self):
264
        r"""Get the number of evaluations made.
265
266
        Returns:
267
                int: Number of evaluations made.
268
269
        """
270
271
        return self.Evals
272
273
    def iters(self):
274
        r"""Get the number of algorithm iteratins made.
275
276
        Returns:
277
                int: Number of generations/iterations made by algorithm.
278
279
        """
280
281
        return self.Iters
282
283
    def nextIter(self):
284
        r"""Increases the number of algorithm iterations made.
285
286
        This function increments number of algorithm iterations/generations counter `self.Iters`.
287
288
        """
289
290
        self.Iters += 1
291
292
293
class StoppingTask(CountingTask):
294
    r"""Optimization task with implemented checking for stopping criterias.
295
296
    Attributes:
297
            nGEN (int): Maximum number of algorithm iterations/generations.
298
            nFES (int): Maximum number of function evaluations.
299
            refValue (float): Reference function/fitness values to reach in optimization.
300
            x (numpy.ndarray): Best found individual.
301
            x_f (float): Best found individual function/fitness value.
302
303
    See Also:
304
            * :class:`NiaPy.util.CountingTask`
305
306
    """
307
308
    def __init__(self, nFES=inf, nGEN=inf, refValue=None, logger=False, **kwargs):
309
        r"""Initialize task class for optimization.
310
311
        Arguments:
312
                nFES (Optional[int]): Number of function evaluations.
313
                nGEN (Optional[int]): Number of generations or iterations.
314
                refValue (Optional[float]): Reference value of function/fitness function.
315
                logger (Optional[bool]): Enable/disable logging of improvements.
316
317
        Note:
318
                Storing improvements during the evolutionary cycle is
319
                captured in self.n_evals and self.x_f_vals
320
321
        See Also:
322
                * :func:`NiaPy.util.CountingTask.__init__`
323
324
        """
325
326
        CountingTask.__init__(self, **kwargs)
327
        self.refValue = (-inf if refValue is None else refValue)
328
        self.logger = logger
329
        self.x, self.x_f = None, inf
330
        self.nFES, self.nGEN = nFES, nGEN
331
        self.n_evals = []
332
        self.x_f_vals = []
333
334
    def eval(self, A):
335
        r"""Evaluate solution.
336
337
        Args:
338
                A (numpy.ndarray): Solution to evaluate.
339
340
        Returns:
341
                float: Fitness/function value of solution.
342
343
        See Also:
344
                * :func:`NiaPy.util.StoppingTask.stopCond`
345
                * :func:`NiaPy.util.CountingTask.eval`
346
347
        """
348
349
        if self.stopCond():
350
            return inf * self.optType.value
351
352
        x_f = CountingTask.eval(self, A)
353
354
        if x_f < self.x_f:
355
            self.x_f = x_f
356
            self.n_evals.append(self.Evals)
357
            self.x_f_vals.append(x_f)
358
            if self.logger:
359
                logger.info('nFES:%d => %s' % (self.Evals, self.x_f))
360
361
        return x_f
362
363
    def stopCond(self):
364
        r"""Check if stopping condition reached.
365
366
        Returns:
367
                bool: `True` if number of function evaluations or number of algorithm iterations/generations or reference values is reach else `False`
368
369
        """
370
371
        return (self.Evals >= self.nFES) or (self.Iters >= self.nGEN) or (self.refValue > self.x_f)
372
373
    def stopCondI(self):
374
        r"""Check if stopping condition reached and increase number of iterations.
375
376
        Returns:
377
                bool: `True` if number of function evaluations or number of algorithm iterations/generations or reference values is reach else `False`.
378
379
        See Also:
380
                * :func:`NiaPy.util.StoppingTask.stopCond`
381
                * :func:`NiaPy.util.CountingTask.nextIter`
382
383
        """
384
385
        r = self.stopCond()
386
        CountingTask.nextIter(self)
387
        return r
388
389
    def return_conv(self):
390
        r"""Get values of x and y axis for plotting covariance graph.
391
392
        Returns:
393
                Tuple[List[int], List[float]]:
394
                    1. List of ints of function evaluations.
395
                    2. List of ints of function/fitness values.
396
397
        """
398
        return self.n_evals, self.x_f_vals
399
400
    def plot(self):
401
        """Plot a simple convergence graph."""
402
        plt.plot(self.n_evals, self.x_f_vals)
403
        plt.xlabel('nFes')
404
        plt.ylabel('Fitness')
405
        plt.title('Convergence graph')
406
        plt.show()
407
408
409
class ThrowingTask(StoppingTask):
410
    r"""Task that throw exceptions when stopping condition is meet.
411
412
    See Also:
413
            * :class:`NiaPy.util.StoppingTask`
414
415
    """
416
417
    def __init__(self, **kwargs):
418
        r"""Initialize optimization task.
419
420
        Args:
421
                **kwargs (Dict[str, Any]): Additional arguments.
422
423
        See Also:
424
                * :func:`NiaPy.util.StoppingTask.__init__`
425
426
        """
427
428
        StoppingTask.__init__(self, **kwargs)
429
430
    def stopCondE(self):
431
        r"""Throw exception for the given stopping condition.
432
433
        Raises:
434
                * FesException: Thrown when the number of function/fitness evaluations is reached.
435
                * GenException: Thrown when the number of algorithms generations/iterations is reached.
436
                * RefException: Thrown when the reference values is reached.
437
                * TimeException: Thrown when algorithm exceeds time run limit.
438
439
        """
440
441
        # dtime = datetime.now() - self.startTime
442
        if self.Evals >= self.nFES:
443
            raise FesException()
444
        if self.Iters >= self.nGEN:
445
            raise GenException()
446
        # if self.runTime is not None and self.runTime >= dtime: raise TimeException()
447
        if self.refValue >= self.x_f:
448
            raise RefException()
449
450
    def eval(self, A):
451
        r"""Evaluate solution.
452
453
        Args:
454
                A (numpy.ndarray): Solution to evaluate.
455
456
        Returns:
457
                float: Function/fitness values of solution.
458
459
        See Also:
460
                * :func:`NiaPy.util.ThrowingTask.stopCondE`
461
                * :func:`NiaPy.util.StoppingTask.eval`
462
463
        """
464
465
        self.stopCondE()
466
        return StoppingTask.eval(self, A)
467