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 minfx optimisation library at U{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, 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 verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
64 @type verbosity: int
65 @keyword print_prefix: The text to place before the printed output.
66 @type print_prefix: str
67 @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.
68 @rtype: tuple of numpy rank-1 array, float, int, str
69 """
70
71
72 if num_incs == None and incs == None:
73 raise MinfxError("Either the incs arg or the num_incs, lower, and upper args must be supplied.")
74 elif num_incs:
75
76 if not isinstance(num_incs, list):
77 raise MinfxError("The num_incs argument '%s' must be a list." % num_incs)
78 if not isinstance(lower, list):
79 raise MinfxError("The lower argument '%s' must be a list." % lower)
80 if not isinstance(upper, list):
81 raise MinfxError("The upper argument '%s' must be a list." % upper)
82
83
84 if len(num_incs) != len(lower):
85 raise MinfxError("The '%s' num_incs and '%s' lower arguments are of different lengths" % (num_incs, lower))
86 if len(num_incs) != len(upper):
87 raise MinfxError("The '%s' num_incs and '%s' upper arguments are of different lengths" % (num_incs, upper))
88
89
90 if num_incs == [] or incs == []:
91
92 if verbosity:
93 print("Cannot run a grid search on a model with zero parameters, directly calculating the function value.")
94
95
96 x0 = zeros(0, float64)
97
98
99 fk = func(x0)
100
101
102 return x0, fk, 1, "No optimisation"
103
104
105
106 if num_incs:
107 n = len(num_incs)
108 else:
109 n = len(incs)
110 grid_size = 0
111 total_steps = 1
112 step_num = ones(n, int)
113 params = zeros((n), float64)
114 min_params = zeros((n), float64)
115
116
117
118 if num_incs:
119 incs = []
120
121
122 for k in range(n):
123 params[k] = lower[k]
124 min_params[k] = lower[k]
125 total_steps = total_steps * num_incs[k]
126 incs.append([])
127
128
129 for i in range(num_incs[k]):
130
131 if num_incs[k] == 1:
132 incs[k].append((lower[k] + upper[k]) / 2.0)
133
134
135 else:
136 incs[k].append(lower[k] + i * (upper[k] - lower[k]) / (num_incs[k] - 1))
137
138
139 else:
140 for k in range(n):
141 total_steps = total_steps * len(incs[k])
142
143
144 if verbosity:
145 if verbosity >= 2:
146 print(print_prefix)
147 print(print_prefix)
148 print(print_prefix + "Grid search")
149 print(print_prefix + "~~~~~~~~~~~")
150
151
152 if A != None and b != None:
153 constraint_flag = 1
154 constraint_linear = Constraint_linear(A, b)
155 c = constraint_linear.func
156 if verbosity >= 3:
157 print(print_prefix + "Linear constraint matrices.")
158 print(print_prefix + "A: " + repr(A))
159 print(print_prefix + "b: " + repr(b))
160
161
162 elif l != None and u != None:
163 constraint_flag = 1
164 raise MinfxError("Bound constraints are not implemented yet.")
165
166
167 elif c != None:
168 constraint_flag = 1
169
170
171 else:
172 constraint_flag = 0
173
174
175 f_min = 1e300
176
177
178 if verbosity:
179 print("\n" + print_prefix + "Searching through %s grid nodes." % total_steps)
180
181
182 if total_steps >= 1e8:
183 raise MinfxError("A grid search of size %s is too large." % total_steps)
184
185
186 k = 0
187 for i in range(total_steps):
188
189 skip = 0
190 if constraint_flag:
191 ci = c(params)
192 if min(ci) < 0.0:
193 if verbosity >= 3:
194 print_iter(k, min_params, print_prefix=print_prefix)
195 print(print_prefix + "Constraint violated, skipping grid point.")
196 print(print_prefix + "ci: " + repr(ci))
197 print("")
198 skip = 1
199
200
201 if not skip:
202
203 f = func(*(params,)+args)
204
205
206 if f < f_min:
207 f_min = f
208 min_params = 1.0 * params
209
210
211 if verbosity:
212 print_iter(k=k, xk=min_params, fk=f_min, print_prefix=print_prefix)
213
214
215 grid_size = grid_size + 1
216
217
218 if verbosity >= 2:
219 if f != f_min:
220 print_iter(k=k, xk=min_params, fk=f, print_prefix=print_prefix)
221 if verbosity >= 3:
222 print(print_prefix + "%-20s%-20s" % ("Increment:", repr(step_num)))
223 print(print_prefix + "%-20s%-20s" % ("Params:", repr(params)))
224 print(print_prefix + "%-20s%-20s" % ("Min params:", repr(min_params)))
225 print(print_prefix + "%-20s%-20g\n" % ("f:", f))
226 print(print_prefix + "%-20s%-20g\n" % ("Min f:", f_min))
227
228
229 k = k + 1
230
231
232 for j in range(n):
233 if step_num[j] < len(incs[j]):
234 step_num[j] = step_num[j] + 1
235 params[j] = incs[j][step_num[j]-1]
236 break
237 else:
238 step_num[j] = 1
239 params[j] = incs[j][0]
240
241
242 return min_params, f_min, grid_size, None
243
244
246 """The grid search algorithm.
247
248 @param func: The target function. This should take the parameter vector as the first argument and return a single float.
249 @type func: function
250 @keyword args: A tuple of arguments to pass to the function, if needed.
251 @type args: tuple
252 @keyword points: The array of grid points to search over.
253 @type points: list or array of lists of float
254 @keyword verbosity: The verbosity level. 0 corresponds to no output, 1 is standard, and higher values cause greater and greater amount of output.
255 @type verbosity: int
256 @keyword print_prefix: The text to place before the printed output.
257 @type print_prefix: str
258 @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.
259 @rtype: tuple of numpy rank-1 array, float, int, str
260 """
261
262
263 total_steps = len(points)
264 n = len(points[0])
265
266
267 f_min = 1e300
268
269
270 if verbosity:
271 print("\n" + print_prefix + "Searching through %s grid nodes." % total_steps)
272
273
274 if total_steps >= 1e8:
275 raise MinfxError("A grid search of size %s is too large." % total_steps)
276
277
278 for k in range(total_steps):
279
280 f = func(*(points[k],)+args)
281
282
283 if f < f_min:
284
285 f_min = f
286 min_params = 1.0 * points[k]
287
288
289 if verbosity:
290 print_iter(k=k, xk=min_params, fk=f_min, print_prefix=print_prefix)
291
292
293 if verbosity >= 2:
294 if f != f_min:
295 print_iter(k=k, xk=min_params, fk=f, print_prefix=print_prefix)
296 if verbosity >= 3:
297 print(print_prefix + "%-20s%-20s" % ("Params:", repr(points[k])))
298 print(print_prefix + "%-20s%-20s" % ("Min params:", repr(min_params)))
299 print(print_prefix + "%-20s%-20g\n" % ("f:", f))
300 print(print_prefix + "%-20s%-20g\n" % ("Min f:", f_min))
301
302
303 return min_params, f_min, total_steps, None
304
305
306 -def grid_split(divisions=None, lower=None, upper=None, inc=None, A=None, b=None, l=None, u=None, c=None):
307 """Generator method yielding arrays of grid points.
308
309 This method will loop over the grid points one-by-one, generating a list of points and yielding these for each subdivision.
310
311
312 @keyword divisions: The number of grid subdivisions.
313 @type divisions: int
314 @keyword lower: The lower bounds of the grid search which must be equal to the number of parameters in the model.
315 @type lower: array of numbers
316 @keyword upper: The upper bounds of the grid search which must be equal to the number of parameters in the model.
317 @type upper: array of numbers
318 @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.
319 @type inc: array of int
320 @keyword A: The linear constraint matrix A, such that A.x >= b.
321 @type A: numpy rank-2 array
322 @keyword b: The linear constraint scalar vectors, such that A.x >= b.
323 @type b: numpy rank-1 array
324 @keyword l: The lower bound constraint vector, such that l <= x <= u.
325 @type l: list of float
326 @keyword u: The upper bound constraint vector, such that l <= x <= u.
327 @type u: list of float
328 @keyword c: A user supplied constraint function.
329 @type c: function
330 @return: A list of grid points for each subdivision is yielded.
331 @rtype: list of list of float
332 """
333
334
335 n = len(inc)
336
337
338 if A != None and b != None:
339 constraint_flag = True
340 constraint_linear = Constraint_linear(A, b)
341 c = constraint_linear.func
342 if verbosity >= 3:
343 print(print_prefix + "Linear constraint matrices.")
344 print(print_prefix + "A: " + repr(A))
345 print(print_prefix + "b: " + repr(b))
346
347
348 elif l != None and u != None:
349 constraint_flag = True
350 raise MinfxError("Bound constraints are not implemented yet.")
351
352
353 elif c != None:
354 constraint_flag = True
355
356
357 else:
358 constraint_flag = False
359
360
361 total_pts = 1
362 for i in range(n):
363 total_pts = total_pts * inc[i]
364
365
366 indices = zeros(n, int)
367 pts = zeros((total_pts, n), float64)
368
369
370 incs = []
371 for k in range(n):
372 incs.append([])
373 for i in range(inc[k]):
374 incs[k].append(lower[k] + i * (upper[k] - lower[k]) / (inc[k] - 1))
375
376
377 for i in range(total_pts):
378
379 for j in range(n):
380
381 pts[i][j] = incs[j][indices[j]]
382
383
384 for j in range(n):
385 if indices[j] < inc[j]-1:
386 indices[j] += 1
387 break
388 else:
389 indices[j] = 0
390
391
392 if constraint_flag:
393
394 pts_trimmed = []
395 for i in range(total_pts):
396
397 ci = c(pts[i])
398
399
400 if min(ci) >= 0.0:
401 pts_trimmed.append(pts[i])
402
403
404 else:
405 pts_trimmed = pts
406
407
408 pts_trimmed = array(pts_trimmed)
409
410
411 total_pts = len(pts_trimmed)
412
413
414 size_float = total_pts / float(divisions)
415 size = int(size_float)
416 if size_float % 1:
417 size = size + 1
418
419
420 for i in range(min(divisions, total_pts)):
421
422 start = i * size
423
424
425 if i != divisions - 1:
426 end = (i + 1) * size
427 else:
428 end = total_pts
429
430
431 yield pts[start: end]
432