1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """The grid search algorithm.
25
26 This file is part of the U{minfx optimisation library<https://sourceforge.net/projects/minfx>}.
27 """
28
29
30 from numpy import array, float64, ones, zeros
31
32
33 from minfx.base_classes import print_iter
34 from minfx.constraint_linear import Constraint_linear
35 from minfx.errors import MinfxError
36
37
38 -def grid(func, args=(), num_incs=None, lower=None, upper=None, incs=None, A=None, b=None, l=None, u=None, c=None, sparseness=None, verbosity=0, print_prefix=""):
39 """The grid search algorithm.
40
41 @param func: The target function. This should take the parameter vector as the first argument and return a single float.
42 @type func: function
43 @keyword args: A tuple of arguments to pass to the function, if needed.
44 @type args: tuple
45 @keyword num_incs: The number of linear increments to be used in the grid search. The length should be equal to the number of parameters and each element corresponds to the number of increments for the respective parameter. This is overridden if the incs argument is supplied.
46 @type num_incs: list of int
47 @keyword lower: The list of lower bounds for the linear grid search. This must be supplied if incs is not.
48 @type lower: list of float
49 @keyword upper: The list of upper bounds for the linear grid search. This must be supplied if incs is not.
50 @type upper: list of float
51 @keyword incs: The parameter increment values. This overrides the num_incs, lower, and upper arguments used in generating a linear grid.
52 @type incs: list of lists
53 @keyword A: The linear constraint matrix A, such that A.x >= b.
54 @type A: numpy rank-2 array
55 @keyword b: The linear constraint scalar vectors, such that A.x >= b.
56 @type b: numpy rank-1 array
57 @keyword l: The lower bound constraint vector, such that l <= x <= u.
58 @type l: list of float
59 @keyword u: The upper bound constraint vector, such that l <= x <= u.
60 @type u: list of float
61 @keyword c: A user supplied constraint function.
62 @type c: function
63 @keyword sparseness: An optional argument for defining sparsity, or regions of the grid to not search over. This is a list whereby each element is a list of two parameters indices. These two parameters will then be assumed to be decoupled and the grid search space between them will be skipped. Symmetry is not observed, so if [0, 1] is sent in, then maybe [1, 0] should be as well.
64 @type sparseness: list of list of int
65 @keyword verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
66 @type verbosity: int
67 @keyword print_prefix: The text to place before the printed output.
68 @type print_prefix: str
69 @return: The optimisation information including the parameter vector at the best grid point, the function value at this grid point, the number of iterations (equal to the number of function calls), and a warning.
70 @rtype: tuple of numpy rank-1 array, float, int, str
71 """
72
73
74 if num_incs == None and incs == None:
75 raise MinfxError("Either the incs arg or the num_incs, lower, and upper args must be supplied.")
76 elif num_incs:
77
78 if not isinstance(num_incs, list):
79 raise MinfxError("The num_incs argument '%s' must be a list." % num_incs)
80 if not isinstance(lower, list):
81 raise MinfxError("The lower argument '%s' must be a list." % lower)
82 if not isinstance(upper, list):
83 raise MinfxError("The upper argument '%s' must be a list." % upper)
84
85
86 if len(num_incs) != len(lower):
87 raise MinfxError("The '%s' num_incs and '%s' lower arguments are of different lengths" % (num_incs, lower))
88 if len(num_incs) != len(upper):
89 raise MinfxError("The '%s' num_incs and '%s' upper arguments are of different lengths" % (num_incs, upper))
90
91
92 if num_incs == [] or incs == []:
93
94 if verbosity:
95 print("Cannot run a grid search on a model with zero parameters, directly calculating the function value.")
96
97
98 x0 = zeros(0, float64)
99
100
101 fk = func(x0)
102
103
104 return x0, fk, 1, "No optimisation"
105
106
107
108 if num_incs:
109 n = len(num_incs)
110 else:
111 n = len(incs)
112 grid_size = 0
113 total_steps = 1
114 step_num = ones(n, int)
115 min_step = ones(n, int)
116 params = zeros((n), float64)
117 min_params = zeros((n), float64)
118 sparseness_flag = False
119 if sparseness != None and len(sparseness):
120 sparseness_flag = True
121
122
123
124 if num_incs:
125 incs = []
126
127
128 for k in range(n):
129 params[k] = lower[k]
130 min_params[k] = lower[k]
131 total_steps = total_steps * num_incs[k]
132 incs.append([])
133
134
135 for i in range(num_incs[k]):
136
137 if num_incs[k] == 1:
138 incs[k].append((lower[k] + upper[k]) / 2.0)
139
140
141 else:
142 incs[k].append(lower[k] + i * (upper[k] - lower[k]) / (num_incs[k] - 1))
143
144
145 else:
146 for k in range(n):
147 total_steps = total_steps * len(incs[k])
148
149
150 if verbosity:
151 if verbosity >= 2:
152 print(print_prefix)
153 print(print_prefix)
154 print(print_prefix + "Grid search")
155 print(print_prefix + "~~~~~~~~~~~")
156
157
158 if A != None and b != None:
159 constraint_flag = 1
160 constraint_linear = Constraint_linear(A, b)
161 c = constraint_linear.func
162 if verbosity >= 3:
163 print(print_prefix + "Linear constraint matrices.")
164 print(print_prefix + "A: " + repr(A))
165 print(print_prefix + "b: " + repr(b))
166
167
168 elif l != None and u != None:
169 constraint_flag = 1
170 raise MinfxError("Bound constraints are not implemented yet.")
171
172
173 elif c != None:
174 constraint_flag = 1
175
176
177 else:
178 constraint_flag = 0
179
180
181 f_min = 1e300
182
183
184 if verbosity:
185 print("\n" + print_prefix + "Searching through %s grid nodes." % total_steps)
186
187
188 if total_steps >= 1e8:
189 raise MinfxError("A grid search of size %s is too large." % total_steps)
190
191
192 k = 0
193 curr_dim = 0
194 for i in range(total_steps):
195
196 skip = False
197
198
199 if constraint_flag:
200 ci = c(params)
201 if min(ci) < 0.0:
202 if verbosity >= 3:
203 print_iter(k, min_params, print_prefix=print_prefix)
204 print(print_prefix + "Constraint violated, skipping grid point.")
205 print(print_prefix + "ci: " + repr(ci))
206 print("")
207 skip = True
208
209
210 if sparseness_flag:
211
212 for block in sparseness:
213
214 if curr_dim == block[0]:
215 if step_num[block[1]] != min_step[block[1]]:
216 skip = True
217 break
218
219
220 min_found = False
221 if not skip:
222
223 f = func(*(params,)+args)
224
225
226 if f < f_min:
227 f_min = f
228 min_params = 1.0 * params
229 min_step = 1.0 * step_num
230 min_found = True
231
232
233 if verbosity:
234 print_iter(k=k, xk=min_params, fk=f_min, print_prefix=print_prefix)
235
236
237 grid_size = grid_size + 1
238
239
240 if verbosity >= 2:
241 if not min_found:
242 print_iter(k=k, xk=min_params, fk=f, print_prefix=print_prefix)
243 if verbosity >= 3:
244 if min_found:
245 print(print_prefix + "Minimum found.")
246 print(print_prefix + "%-20s%-20s" % ("Increment:", step_num))
247 print(print_prefix + "%-20s%-20s" % ("Current dimension:", curr_dim))
248 print(print_prefix + "%-20s%-20s" % ("Params:", params))
249 print(print_prefix + "%-20s%-20s" % ("Min params:", min_params))
250 print(print_prefix + "%-20s%-20s" % ("Min indices:", min_step))
251 print(print_prefix + "%-20s%-20.8g" % ("f:", f))
252 print(print_prefix + "%-20s%-20.8g\n" % ("Min f:", f_min))
253
254
255 k = k + 1
256
257
258 for j in range(n):
259 if step_num[j] < len(incs[j]):
260 step_num[j] = step_num[j] + 1
261 params[j] = incs[j][step_num[j]-1]
262 if j > curr_dim:
263 curr_dim = j
264 break
265 else:
266 step_num[j] = 1
267 params[j] = incs[j][0]
268
269
270 return min_params, f_min, grid_size, None
271
272
273 -def grid_point_array(func, args=(), points=None, A=None, b=None, l=None, u=None, c=None, verbosity=0, print_prefix=""):
274 """The grid search algorithm.
275
276 @param func: The target function. This should take the parameter vector as the first argument and return a single float.
277 @type func: function
278 @keyword args: A tuple of arguments to pass to the function, if needed.
279 @type args: tuple
280 @keyword points: The array of grid points to search over.
281 @type points: list or array of lists of float
282 @keyword A: The linear constraint matrix A, such that A.x >= b.
283 @type A: numpy rank-2 array
284 @keyword b: The linear constraint scalar vectors, such that A.x >= b.
285 @type b: numpy rank-1 array
286 @keyword l: The lower bound constraint vector, such that l <= x <= u.
287 @type l: list of float
288 @keyword u: The upper bound constraint vector, such that l <= x <= u.
289 @type u: list of float
290 @keyword c: A user supplied constraint function.
291 @type c: function
292 @keyword verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
293 @type verbosity: int
294 @keyword print_prefix: The text to place before the printed output.
295 @type print_prefix: str
296 @return: The optimisation information including the parameter vector at the best grid point, the function value at this grid point, the number of iterations (equal to the number of function calls), and a warning.
297 @rtype: tuple of numpy rank-1 array, float, int, str
298 """
299
300
301 total_steps = len(points)
302
303
304 if total_steps == 0:
305 return None, None, None, None
306
307
308 n = len(points[0])
309
310
311 f_min = 1e300
312
313
314 if verbosity:
315 print("\n" + print_prefix + "Searching through %s grid nodes." % total_steps)
316
317
318 if total_steps >= 1e8:
319 raise MinfxError("A grid search of size %s is too large." % total_steps)
320
321
322 if A != None and b != None:
323 constraint_linear = Constraint_linear(A, b)
324 c = constraint_linear.func
325 if verbosity >= 3:
326 print(print_prefix + "Linear constraint matrices.")
327 print(print_prefix + "A: " + repr(A))
328 print(print_prefix + "b: " + repr(b))
329
330
331 elif l != None and u != None:
332 raise MinfxError("Bound constraints are not implemented yet.")
333
334
335 constraint_flag = False
336 if c != None:
337 constraint_flag = True
338
339
340 for k in range(total_steps):
341
342 if constraint_flag:
343 ci = c(params)
344 if min(ci) < 0.0:
345 if verbosity >= 3:
346 print_iter(k, min_params, print_prefix=print_prefix)
347 print(print_prefix + "Constraint violated, skipping grid point.")
348 print(print_prefix + "ci: " + repr(ci))
349 print("")
350 continue
351
352
353 f = func(*(points[k],)+args)
354
355
356 if f < f_min:
357
358 f_min = f
359 min_params = 1.0 * points[k]
360
361
362 if verbosity:
363 print_iter(k=k, xk=min_params, fk=f_min, print_prefix=print_prefix)
364
365
366 if verbosity >= 2:
367 if f != f_min:
368 print_iter(k=k, xk=min_params, fk=f, print_prefix=print_prefix)
369 if verbosity >= 3:
370 print(print_prefix + "%-20s%-20s" % ("Params:", repr(points[k])))
371 print(print_prefix + "%-20s%-20s" % ("Min params:", repr(min_params)))
372 print(print_prefix + "%-20s%-20g\n" % ("f:", f))
373 print(print_prefix + "%-20s%-20g\n" % ("Min f:", f_min))
374
375
376 return min_params, f_min, total_steps, None
377
378
379 -def grid_split(divisions=None, lower=None, upper=None, inc=None, A=None, b=None, l=None, u=None, c=None):
380 """Generator method yielding arrays of grid points.
381
382 This method will loop over the grid points one-by-one, generating a list of points and yielding these for each subdivision.
383
384
385 @keyword divisions: The number of grid subdivisions.
386 @type divisions: int
387 @keyword lower: The lower bounds of the grid search which must be equal to the number of parameters in the model.
388 @type lower: array of numbers
389 @keyword upper: The upper bounds of the grid search which must be equal to the number of parameters in the model.
390 @type upper: array of numbers
391 @keyword inc: The increments for each dimension of the space for the grid search. The number of elements in the array must equal to the number of parameters in the model.
392 @type inc: array of int
393 @keyword A: The linear constraint matrix A, such that A.x >= b.
394 @type A: numpy rank-2 array
395 @keyword b: The linear constraint scalar vectors, such that A.x >= b.
396 @type b: numpy rank-1 array
397 @keyword l: The lower bound constraint vector, such that l <= x <= u.
398 @type l: list of float
399 @keyword u: The upper bound constraint vector, such that l <= x <= u.
400 @type u: list of float
401 @keyword c: A user supplied constraint function.
402 @type c: function
403 @return: A list of grid points for each subdivision is yielded.
404 @rtype: list of list of float
405 """
406
407
408 n = len(inc)
409
410
411 if A != None and b != None:
412 constraint_flag = True
413 constraint_linear = Constraint_linear(A, b)
414 c = constraint_linear.func
415 if verbosity >= 3:
416 print(print_prefix + "Linear constraint matrices.")
417 print(print_prefix + "A: " + repr(A))
418 print(print_prefix + "b: " + repr(b))
419
420
421 elif l != None and u != None:
422 constraint_flag = True
423 raise MinfxError("Bound constraints are not implemented yet.")
424
425
426 elif c != None:
427 constraint_flag = True
428
429
430 else:
431 constraint_flag = False
432
433
434 total_pts = 1
435 for i in range(n):
436 total_pts = total_pts * inc[i]
437
438
439 indices = zeros(n, int)
440 pts = zeros((total_pts, n), float64)
441
442
443 incs = []
444 for k in range(n):
445 incs.append([])
446 for i in range(inc[k]):
447 incs[k].append(lower[k] + i * (upper[k] - lower[k]) / (inc[k] - 1))
448
449
450 for i in range(total_pts):
451
452 for j in range(n):
453
454 pts[i][j] = incs[j][indices[j]]
455
456
457 for j in range(n):
458 if indices[j] < inc[j]-1:
459 indices[j] += 1
460 break
461 else:
462 indices[j] = 0
463
464
465 if constraint_flag:
466
467 pts_trimmed = []
468 for i in range(total_pts):
469
470 ci = c(pts[i])
471
472
473 if min(ci) >= 0.0:
474 pts_trimmed.append(pts[i])
475
476
477 else:
478 pts_trimmed = pts
479
480
481 pts_trimmed = array(pts_trimmed)
482
483
484 total_pts = len(pts_trimmed)
485
486
487 size_float = total_pts / float(divisions)
488 size = int(size_float)
489 if size_float % 1:
490 size = size + 1
491
492
493 for i in range(min(divisions, total_pts)):
494
495 start = i * size
496
497
498 if i != divisions - 1:
499 end = (i + 1) * size
500 else:
501 end = total_pts
502
503
504 yield pts[start: end]
505