Passed
Branch master (13db34)
by Grega
02:16
created

NiaPy.Runner.__exportToLatex()   F

Complexity

Conditions 14

Size

Total Lines 53
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 46
nop 1
dl 0
loc 53
rs 3.6
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like NiaPy.Runner.__exportToLatex() 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.

1
# encoding=utf8
2
# pylint: disable=mixed-indentation, line-too-long, multiple-statements, too-many-function-args, singleton-comparison, bad-continuation
3
"""Python micro framework for building nature-inspired algorithms."""
4
5
from __future__ import print_function  # for backward compatibility purpose
6
7
import os
8
import logging
9
import json
10
import datetime
11
import xlsxwriter
12
from numpy import amin, amax, median, mean, std
13
from NiaPy import benchmarks, util, algorithms
14
from NiaPy.algorithms import basic as balgos, modified as malgos, other as oalgos
15
16
__all__ = ['algorithms', 'benchmarks', 'util']
17
__project__ = 'NiaPy'
18
__version__ = '2.0.0rc4'
19
20
VERSION = "{0} v{1}".format(__project__, __version__)
21
22
logging.basicConfig()
23
logger = logging.getLogger('NiaPy')
24
logger.setLevel('INFO')
25
26
NiaPyAlgos = [
27
	balgos.BatAlgorithm,
28
	balgos.DifferentialEvolution,
29
	balgos.CrowdingDifferentialEvolution,
30
	balgos.DynNpDifferentialEvolution,
31
	balgos.AgingNpDifferentialEvolution,
32
	balgos.MultiStrategyDifferentialEvolution,
33
	balgos.DynNpMultiStrategyDifferentialEvolution,
34
	balgos.AgingNpMultiMutationDifferentialEvolution,
35
	balgos.FireflyAlgorithm,
36
	balgos.FlowerPollinationAlgorithm,
37
	balgos.GreyWolfOptimizer,
38
	balgos.ArtificialBeeColonyAlgorithm,
39
	balgos.GeneticAlgorithm,
40
	balgos.ParticleSwarmAlgorithm,
41
	balgos.CamelAlgorithm,
42
	balgos.BareBonesFireworksAlgorithm,
43
	balgos.MonkeyKingEvolutionV1,
44
	balgos.MonkeyKingEvolutionV2,
45
	balgos.MonkeyKingEvolutionV3,
46
	balgos.EvolutionStrategy1p1,
47
	balgos.EvolutionStrategyMp1,
48
	balgos.EvolutionStrategyMpL,
49
	balgos.SineCosineAlgorithm,
50
	balgos.HarmonySearch,
51
	balgos.HarmonySearchV1,
52
	balgos.GlowwormSwarmOptimization,
53
	balgos.GlowwormSwarmOptimizationV1,
54
	balgos.GlowwormSwarmOptimizationV2,
55
	balgos.GlowwormSwarmOptimizationV3,
56
	balgos.KrillHerdV1,
57
	balgos.KrillHerdV2,
58
	balgos.KrillHerdV3,
59
	balgos.KrillHerdV4,
60
	balgos.KrillHerdV11,
61
	balgos.FireworksAlgorithm,
62
	balgos.EnhancedFireworksAlgorithm,
63
	balgos.DynamicFireworksAlgorithm,
64
	balgos.DynamicFireworksAlgorithmGauss,
65
	balgos.GravitationalSearchAlgorithm,
66
	balgos.FishSchoolSearch,
67
	balgos.MothFlameOptimizer,
68
	balgos.CuckooSearch,
69
	balgos.CovarianceMatrixAdaptionEvolutionStrategy,
70
	balgos.CoralReefsOptimization,
71
	balgos.ForestOptimizationAlgorithm
72
]
73
74
NiaPyAlgos += [
75
	malgos.HybridBatAlgorithm,
76
	malgos.DifferentialEvolutionMTS,
77
	malgos.DifferentialEvolutionMTSv1,
78
	malgos.DynNpDifferentialEvolutionMTS,
79
	malgos.DynNpDifferentialEvolutionMTSv1,
80
	malgos.MultiStrategyDifferentialEvolutionMTS,
81
	malgos.MultiStrategyDifferentialEvolutionMTSv1,
82
	malgos.DynNpMultiStrategyDifferentialEvolutionMTS,
83
	malgos.DynNpMultiStrategyDifferentialEvolutionMTSv1,
84
	malgos.SelfAdaptiveDifferentialEvolution,
85
	malgos.DynNpSelfAdaptiveDifferentialEvolutionAlgorithm,
86
	malgos.MultiStrategySelfAdaptiveDifferentialEvolution,
87
	malgos.DynNpMultiStrategySelfAdaptiveDifferentialEvolution
88
]
89
90
NiaPyAlgos += [
91
	oalgos.MultipleTrajectorySearch,
92
	oalgos.MultipleTrajectorySearchV1,
93
	oalgos.NelderMeadMethod,
94
	oalgos.HillClimbAlgorithm,
95
	oalgos.SimulatedAnnealing,
96
	oalgos.AnarchicSocietyOptimization,
97
	# oalgos.TabuSearch
98
]
99
100
class Runner:
101
	r"""Runner utility feature.
102
103
	Feature which enables running multiple algorithms with multiple benchmarks.
104
	It also support exporting results in various formats (e.g. LaTeX, Excel, JSON)
105
106
	Attributes:
107
      D (int): Dimension of problem
108
		NP (int): Population size
109
		nFES (int): Number of function evaluations
110
		nRuns (int): Number of repetitions
111
		useAlgorithms (list of Algorithm): List of algorithms to run
112
		useBenchmarks (list of Benchmarks): List of benchmarks to run
113
		results (): TODO
114
	"""
115
	def __init__(self, D=10, nFES=1000000, nGEN=100000, useAlgorithms='ArtificialBeeColonyAlgorithm', useBenchmarks='Ackley', **kwargs):
116
		r"""Initialize Runner.
117
118
		**__init__(self, D, NP, nFES, nRuns, useAlgorithms, useBenchmarks, ...)**
119
120
		Arguments:
121
			D (int): Dimension of problem
122
			NP (int): Population size
123
			nFES (int): Number of function evaluations
124
			nRuns (int): Number of repetitions
125
			useAlgorithms (list of Algorithm): List of algorithms to run
126
			useBenchmarks (list of Benchmarks): List of benchmarks to run
127
128
		Keyword Args:
129
			A (float): Laudness
130
			r (float): Pulse rate
131
			Qmin (float): Minimum frequency
132
			Qmax (float): Maximum frequency
133
			Pa (float): Probability
134
			F (float): Scalling factor
135
			F_l (float): Lower limit of scalling factor
136
			F_u (float): Upper limit of scalling factor
137
			CR (float): Crossover rate
138
			alpha (float): Alpha parameter
139
			betamin (float): Betamin parameter
140
			gamma (float): Gamma parameter
141
			p (float): Probability switch
142
			Ts (float): Tournament selection
143
			Mr (float): Mutation rate
144
			C1 (float): Cognitive component
145
			C2 (float): Social component
146
			w (float): Inertia weight
147
			vMin (float): Minimal velocity
148
			vMax (float): Maximal velocity
149
			Tao1 (float): Probability
150
			Tao2 (float): Probability
151
			n (int): Number of sparks
152
			mu (float): Mu parameter
153
			omega (float): TODO
154
			S_init (float): Initial supply for camel
155
			E_init (float): Initial endurance for camel
156
			T_min (float): Minimal temperature
157
			T_max (float): Maximal temperature
158
			C_a (float): Amplification factor
159
			C_r (float): Reduction factor
160
			Limit (int): Limit
161
			k (int): Number of runs before adaptive
162
		"""
163
		self.D = D
164
		self.nFES = nFES
165
		self.nGEN = nGEN
166
		self.useAlgorithms = useAlgorithms
167
		self.useBenchmarks = useBenchmarks
168
		self.args = kwargs
169
		self.results = {}
170
171
	@staticmethod
172
	def getAlgorithm(name):
173
		r"""Get algorithm for optimization.
174
175
		Args:
176
			name (str): Name of the algorithm
177
178
		Returns:
179
			Algorithm: TODO
180
		"""
181
		algorithm = None
182
		for alg in NiaPyAlgos:
183
			if name in alg.Name: algorithm = alg; break
184
		if algorithm == None: raise TypeError('Passed algorithm is not defined!')
185
		return algorithm
186
187
	def benchmarkFactory(self, name):
188
		r"""Create optimization task.
189
190
		Args:
191
			name (str): Benchmark name.
192
193
		Returns:
194
			Task: Optimization task to use.
195
		"""
196
		return util.Task(D=self.D, nFES=self.nFES, nGEN=self.nGEN, optType=util.OptimizationType.MINIMIZATION, benchmark=name)
197
198
	def algorithmFactory(self, name):
199
		r"""TODO.
200
201
		Args:
202
			name (str): Name of algorithm.
203
204
		Returns:
205
			Algorithm: Initialized algorithm with parameters.
206
		"""
207
		algorithm, params = Runner.getAlgorithm(name), dict()
208
		for k, v in algorithm.typeParameters().items():
209
			val = self.args.get(k, None)
210
			if val != None and v(val): params[k] = val
211
		return algorithm(**params)
212
213
	@classmethod
214
	def __createExportDir(cls):
215
		r"""TODO."""
216
		if not os.path.exists('export'): os.makedirs('export')
217
218
	@classmethod
219
	def __generateExportName(cls, extension):
220
		r"""TODO.
221
222
		Args:
223
			extension:
224
225
		Returns:
226
227
		"""
228
		return 'export/' + str(datetime.datetime.now()).replace(':', '.') + '.' + extension
229
230
	def __exportToLog(self):
231
		r"""TODO."""
232
		print(self.results)
233
234
	def __exportToJson(self):
235
		r"""TODO.
236
237
		See Also:
238
			* :func:`NiaPy.Runner.__createExportDir`
239
		"""
240
		self.__createExportDir()
241
		with open(self.__generateExportName('json'), 'w') as outFile:
242
			json.dump(self.results, outFile)
243
			logger.info('Export to JSON completed!')
244
245
	def __exportToXls(self):
246
		r"""TODO.
247
248
		See Also:
249
			:func:`NiaPy.Runner.__generateExportName`
250
		"""
251
		self.__createExportDir()
252
		workbook = xlsxwriter.Workbook(self.__generateExportName('xlsx'))
253
		worksheet = workbook.add_worksheet()
254
		row, col, nRuns = 0, 0, 0
255
		for alg in self.results:
256
			_, col = worksheet.write(row, col, alg), col + 1
257
			for bench in self.results[alg]:
258
				worksheet.write(row, col, bench)
259
				nRuns = len(self.results[alg][bench])
260
				for i in range(len(self.results[alg][bench])): _, row = worksheet.write(row, col, self.results[alg][bench][i]), row + 1
261
				row, col = row - len(self.results[alg][bench]), col + 1
262
			row, col = row + 1 + nRuns, col - 1 + len(self.results[alg])
263
		workbook.close()
264
		logger.info('Export to XLSX completed!')
265
266
	def __exportToLatex(self):
267
		r"""TODO.
268
269
		See Also:
270
			:func:`NiaPy.Runner.__createExportDir`
271
			:func:`NiaPy.Runner.__generateExportName`
272
		"""
273
		self.__createExportDir()
274
		metrics = ['Best', 'Median', 'Worst', 'Mean', 'Std.']
275
		def only_upper(s): return "".join(c for c in s if c.isupper())
276
		with open(self.__generateExportName('tex'), 'a') as outFile:
277
			outFile.write('\\documentclass{article}\n')
278
			outFile.write('\\usepackage[utf8]{inputenc}\n')
279
			outFile.write('\\usepackage{siunitx}\n')
280
			outFile.write('\\sisetup{\n')
281
			outFile.write('round-mode=places,round-precision=3}\n')
282
			outFile.write('\\begin{document}\n')
283
			outFile.write('\\begin{table}[h]\n')
284
			outFile.write('\\centering\n')
285
			begin_tabular = '\\begin{tabular}{cc'
286
			for alg in self.results:
287
				for _i in range(len(self.results[alg])): begin_tabular += 'S'
288
				firstLine = '   &'
289
				for benchmark in self.results[alg].keys(): firstLine += '  &   \\multicolumn{1}{c}{\\textbf{' + benchmark + '}}'
290
				firstLine += ' \\\\'
291
				break
292
			begin_tabular += '}\n'
293
			outFile.write(begin_tabular)
294
			outFile.write('\\hline\n')
295
			outFile.write(firstLine + '\n')
0 ignored issues
show
introduced by
The variable firstLine does not seem to be defined for all execution paths.
Loading history...
296
			outFile.write('\\hline\n')
297
			for alg in self.results:
298
				for metric in metrics:
299
					line = ''
300
					if metric != 'Worst': line += '   &   ' + metric
301
					else:
302
						shortAlg = ''
303
						if alg.endswith('Algorithm'):	shortAlg = only_upper(alg[:-9])
304
						else: shortAlg = only_upper(alg)
305
						line += '\\textbf{' + shortAlg + '} &   ' + metric
306
						for benchmark in self.results[alg]:
307
							if metric == 'Best':	line += '   &   ' + str(amin(self.results[alg][benchmark]))
308
							elif metric == 'Median': line += '   &   ' + str(median(self.results[alg][benchmark]))
309
							elif metric == 'Worst': line += '   &   ' + str(amax(self.results[alg][benchmark]))
310
							elif metric == 'Mean': line += '   &   ' + str(mean(self.results[alg][benchmark]))
311
							else: line += '   &   ' + str(std(self.results[alg][benchmark]))
312
						line += '   \\\\'
313
						outFile.write(line + '\n')
314
					outFile.write('\\hline\n')
315
				outFile.write('\\end{tabular}\n')
316
				outFile.write('\\end{table}\n')
317
				outFile.write('\\end{document}')
318
		logger.info('Export to Latex completed!')
319
320
	def run(self, export='log', verbose=False):
321
		"""Execute runner.
322
323
		Arguments:
324
			export (str): Takes export type (e.g. log, json, xlsx, latex) (default: 'log')
325
			verbose (bool: Switch for verbose logging (default: {False})
326
327
		Raises:
328
			TypeError: Raises TypeError if export type is not supported
329
330
		Returns:
331
			dict: Returns dictionary of results
332
333
		See Also:
334
			* :func:`NiaPy.Runner.useAlgorithms`
335
			* :func:`NiaPy.Runner.useBenchmarks`
336
			* :func:`NiaPy.Runner.__algorithmFactory`
337
		"""
338
		for alg in self.useAlgorithms:
339
			self.results[alg] = {}
340
			if verbose:	logger.info('Running %s...', alg)
341
			for bench in self.useBenchmarks:
342
				benchName = ''
343
				if not isinstance(bench, ''.__class__): benchName = str(type(bench).__name__)
344
				else: benchName = bench
345
				if verbose: logger.info('Running %s algorithm on %s benchmark...', alg, benchName)
346
				bm = self.benchmarkFactory(bench)
347
				self.results[alg][benchName] = []
348
				for _ in range(self.nGEN):
349
					algorithm = self.algorithmFactory(alg)
350
					self.results[alg][benchName].append(algorithm.run(bm))
351
			if verbose: logger.info('---------------------------------------------------')
352
		if export == 'log': self.__exportToLog()
353
		elif export == 'json': self.__exportToJson()
354
		elif export == 'xlsx': self.__exportToXls()
355
		elif export == 'latex': self.__exportToLatex()
356
		else: raise TypeError('Passed export type is not supported!')
357
		return self.results
358
359
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
360