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://gna.org/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 is not None and b is not 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=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 is not None and b is not 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=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, verbosity=0, print_prefix=""):
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 @keyword verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
404 @type verbosity: int
405 @keyword print_prefix: The text to place before the printed output.
406 @type print_prefix: str
407 @return: A list of grid points for each subdivision is yielded.
408 @rtype: list of list of float
409 """
410
411
412 if verbosity and divisions > 1:
413 print("%sSplitting up the grid points." % print_prefix)
414
415
416 n = len(inc)
417
418
419 if A is not None and b is not None:
420 constraint_flag = True
421 constraint_linear = Constraint_linear(A, b)
422 c = constraint_linear.func
423 if verbosity >= 3:
424 print(print_prefix + "Linear constraint matrices.")
425 print(print_prefix + "A: " + repr(A))
426 print(print_prefix + "b: " + repr(b))
427
428
429 elif l != None and u != None:
430 constraint_flag = True
431 raise MinfxError("Bound constraints are not implemented yet.")
432
433
434 elif c != None:
435 constraint_flag = True
436
437
438 else:
439 constraint_flag = False
440
441
442 total_pts = 1
443 for i in range(n):
444 total_pts = total_pts * inc[i]
445
446
447 indices = zeros(n, int)
448 pts = zeros((total_pts, n), float64)
449
450
451 incs = []
452 for k in range(n):
453 incs.append([])
454 if inc[k] == 1:
455 incs[k].append((lower[k] + upper[k])/2.0)
456 else:
457 for i in range(inc[k]):
458 incs[k].append(lower[k] + i * (upper[k] - lower[k]) / (inc[k] - 1))
459
460
461 for i in range(total_pts):
462
463 for j in range(n):
464
465 pts[i][j] = incs[j][indices[j]]
466
467
468 for j in range(n):
469 if indices[j] < inc[j]-1:
470 indices[j] += 1
471 break
472 else:
473 indices[j] = 0
474
475
476 if constraint_flag:
477
478 pts_trimmed = []
479 for i in range(total_pts):
480
481 ci = c(pts[i])
482
483
484 if min(ci) >= 0.0:
485 pts_trimmed.append(pts[i])
486
487
488 else:
489 pts_trimmed = pts
490
491
492 pts_trimmed = array(pts_trimmed)
493
494
495 total_pts = len(pts_trimmed)
496
497
498 size_float = total_pts / float(divisions)
499 size = int(size_float)
500 if size_float % 1:
501 size = size + 1
502
503
504 if verbosity and divisions > 1:
505 print("%s Total number of grid points: %20i" % (print_prefix, total_pts))
506 print("%s Number of divisions: %20i" % (print_prefix, divisions))
507 print("%s Subdivision size: %20i" % (print_prefix, size))
508
509
510 for i in range(min(divisions, total_pts)):
511
512 start = i * size
513
514
515 if i != divisions - 1:
516 end = (i + 1) * size
517 else:
518 end = total_pts
519
520
521 yield pts_trimmed[start: end]
522
523
524 -def grid_split_array(divisions=None, points=None, A=None, b=None, l=None, u=None, c=None, verbosity=0, print_prefix=""):
525 """Generator method yielding arrays of grid points.
526
527 This method will loop over the grid points one-by-one, generating a list of points and yielding these for each subdivision.
528
529
530 @keyword divisions: The number of grid subdivisions.
531 @type divisions: int
532 @keyword points: The array of grid points to split up.
533 @type points: list of lists or rank-2 numpy array
534 @keyword A: The linear constraint matrix A, such that A.x >= b.
535 @type A: numpy rank-2 array
536 @keyword b: The linear constraint scalar vectors, such that A.x >= b.
537 @type b: numpy rank-1 array
538 @keyword l: The lower bound constraint vector, such that l <= x <= u.
539 @type l: list of float
540 @keyword u: The upper bound constraint vector, such that l <= x <= u.
541 @type u: list of float
542 @keyword c: A user supplied constraint function.
543 @type c: function
544 @keyword verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
545 @type verbosity: int
546 @keyword print_prefix: The text to place before the printed output.
547 @type print_prefix: str
548 @return: A list of grid points for each subdivision is yielded.
549 @rtype: list of list of float
550 """
551
552
553 if verbosity and divisions > 1:
554 print("%sSplitting up the array of grid points." % print_prefix)
555
556
557 if A is not None and b is not None:
558 constraint_flag = True
559 constraint_linear = Constraint_linear(A, b)
560 c = constraint_linear.func
561 if verbosity >= 3:
562 print(print_prefix + "Linear constraint matrices.")
563 print(print_prefix + "A: " + repr(A))
564 print(print_prefix + "b: " + repr(b))
565
566
567 elif l != None and u != None:
568 constraint_flag = True
569 raise MinfxError("Bound constraints are not implemented yet.")
570
571
572 elif c != None:
573 constraint_flag = True
574
575
576 else:
577 constraint_flag = False
578
579
580 if constraint_flag:
581
582 pts_trimmed = []
583 for i in range(len(points)):
584
585 ci = c(points[i])
586
587
588 if min(ci) >= 0.0:
589 pts_trimmed.append(points[i])
590
591
592 else:
593 pts_trimmed = points
594
595
596 pts_trimmed = array(pts_trimmed)
597
598
599 total_pts = len(pts_trimmed)
600
601
602 size_float = total_pts / float(divisions)
603 size = int(size_float)
604 if size_float % 1:
605 size = size + 1
606
607
608 if verbosity and divisions > 1:
609 print("%s Total number of grid points: %20i" % (print_prefix, total_pts))
610 print("%s Number of divisions: %20i" % (print_prefix, divisions))
611 print("%s Subdivision size: %20i" % (print_prefix, size))
612
613
614 for i in range(min(divisions, total_pts)):
615
616 start = i * size
617
618
619 if i != divisions - 1:
620 end = (i + 1) * size
621 else:
622 end = total_pts
623
624
625 yield pts_trimmed[start: end]
626