Skip to content

Commit 3c06255

Browse files
committed
Allow parallel initialization for simple linear models with non-random directions, update unit tests with eval num info
1 parent 0784e65 commit 3c06255

File tree

3 files changed

+151
-119
lines changed

3 files changed

+151
-119
lines changed

dfols/controller.py

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -253,69 +253,101 @@ def initialise_coordinate_directions(self, number_of_samples, num_directions, pa
253253

254254
at_lower_boundary = (self.model.sl > -0.01 * self.delta) # sl = xl - x0, should be -ve, actually < -rhobeg
255255
at_upper_boundary = (self.model.su < 0.01 * self.delta) # su = xu - x0, should be +ve, actually > rhobeg
256-
257-
xpts_added = np.zeros((num_directions + 1, self.n()))
258-
for k in range(1, num_directions + 1):
259-
# k = 0 --> base point (xpt = 0) [ not here]
260-
# k = 1, ..., 2n --> coordinate directions [1,...,n and n+1,...,2n]
261-
# k = 2n+1, ..., (n+1)(n+2)/2 --> off-diagonal directions
262-
if 1 <= k < self.n() + 1: # first step along coord directions
256+
257+
if params("init.run_in_parallel") and num_directions <= self.n():
258+
# Can do all the evaluation in parallel if <= n+1 interpolation points, but if larger
259+
# then the step depends on the function value at previous steps and does point swapping
260+
xpts_added = np.zeros((num_directions + 1, self.n()))
261+
eval_obj_results = []
262+
for k in range(1, num_directions + 1): # k = 1, ..., num_directions
263+
# always have k = 1, ..., n since num_directions <= n
263264
dirn = k - 1 # direction to move in (0,...,n-1)
264265
stepa = self.delta if not at_upper_boundary[dirn] else -self.delta # take a +delta step if at lower, -delta if at upper
265266
stepb = None
266267
xpts_added[k, dirn] = stepa # set new (relative) point to the step since we haven't done any moving, so relative point is all zeros.
268+
269+
# Evaluate objective at this new point
270+
x = self.model.as_absolute_coordinates(xpts_added[k, :])
271+
eval_obj_results.append(self.evaluate_objective(x, number_of_samples, params))
272+
273+
# Evaluations done, now add to the model
274+
for k in range(1, num_directions + 1):
275+
x = self.model.as_absolute_coordinates(xpts_added[k, :])
276+
rvec_list, obj_list, num_samples_run, exit_info = eval_obj_results[k-1]
277+
# Handle exit conditions (f < min obj value or maxfun reached)
278+
if exit_info is not None:
279+
if num_samples_run > 0:
280+
self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
281+
x_in_abs_coords=True)
282+
return exit_info # return & quit
267283

268-
elif self.n() + 1 <= k < 2 * self.n() + 1: # second step along coord directions
269-
dirn = k - self.n() - 1 # direction to move in (0,...,n-1)
270-
stepa = xpts_added[k - self.n(), dirn] # previous step
271-
stepb = -self.delta # new step
272-
if at_lower_boundary[dirn]:
273-
# if at lower boundary, set the second step to be +ve
274-
stepb = min(2.0 * self.delta, self.model.su[dirn]) # su = xu - x0, should be +ve
275-
if at_upper_boundary[dirn]:
276-
# if at upper boundary, set the second step to be -ve
277-
stepb = max(-2.0 * self.delta, self.model.sl[dirn]) # sl = xl - x0, should be -ve
278-
xpts_added[k, dirn] = stepb
279-
280-
else: # k = 2n+1, ..., (n+1)(n+2)/2
281-
# p = (k - 1) % n + 1 # cycles through (1,...,n), starting at 2n+1 --> 1
282-
# l = (k - 2 * n - 1) / n + 1 # (1,...,1, 2, ..., 2, etc.) where each number appears n times
283-
# q = (p + l if p + l <= n else p + l - n)
284-
stepa = None
285-
stepb = None
286-
itemp = (k - self.n() - 1) // self.n()
287-
q = k - itemp * self.n() - self.n()
288-
p = q + itemp
289-
if p > self.n():
290-
p, q = q, p - self.n() # does swap correctly in Python
291-
292-
xpts_added[k, p - 1] = xpts_added[p, p - 1]
293-
xpts_added[k, q - 1] = xpts_added[q, q - 1]
284+
# Otherwise, add new results (increments model.npt_so_far)
285+
self.model.change_point(k, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
286+
for i in range(1, num_samples_run):
287+
self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
288+
else:
289+
xpts_added = np.zeros((num_directions + 1, self.n()))
290+
for k in range(1, num_directions + 1):
291+
# k = 0 --> base point (xpt = 0) [ not here]
292+
# k = 1, ..., 2n --> coordinate directions [1,...,n and n+1,...,2n]
293+
# k = 2n+1, ..., (n+1)(n+2)/2 --> off-diagonal directions
294+
if 1 <= k < self.n() + 1: # first step along coord directions
295+
dirn = k - 1 # direction to move in (0,...,n-1)
296+
stepa = self.delta if not at_upper_boundary[dirn] else -self.delta # take a +delta step if at lower, -delta if at upper
297+
stepb = None
298+
xpts_added[k, dirn] = stepa # set new (relative) point to the step since we haven't done any moving, so relative point is all zeros.
299+
300+
elif self.n() + 1 <= k < 2 * self.n() + 1: # second step along coord directions
301+
dirn = k - self.n() - 1 # direction to move in (0,...,n-1)
302+
stepa = xpts_added[k - self.n(), dirn] # previous step
303+
stepb = -self.delta # new step
304+
if at_lower_boundary[dirn]:
305+
# if at lower boundary, set the second step to be +ve
306+
stepb = min(2.0 * self.delta, self.model.su[dirn]) # su = xu - x0, should be +ve
307+
if at_upper_boundary[dirn]:
308+
# if at upper boundary, set the second step to be -ve
309+
stepb = max(-2.0 * self.delta, self.model.sl[dirn]) # sl = xl - x0, should be -ve
310+
xpts_added[k, dirn] = stepb
311+
312+
else: # k = 2n+1, ..., (n+1)(n+2)/2
313+
# p = (k - 1) % n + 1 # cycles through (1,...,n), starting at 2n+1 --> 1
314+
# l = (k - 2 * n - 1) / n + 1 # (1,...,1, 2, ..., 2, etc.) where each number appears n times
315+
# q = (p + l if p + l <= n else p + l - n)
316+
stepa = None
317+
stepb = None
318+
itemp = (k - self.n() - 1) // self.n()
319+
q = k - itemp * self.n() - self.n()
320+
p = q + itemp
321+
if p > self.n():
322+
p, q = q, p - self.n() # does swap correctly in Python
323+
324+
xpts_added[k, p - 1] = xpts_added[p, p - 1]
325+
xpts_added[k, q - 1] = xpts_added[q, q - 1]
294326

295-
# Evaluate objective at this new point
296-
x = self.model.as_absolute_coordinates(xpts_added[k, :])
297-
rvec_list, obj_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
327+
# Evaluate objective at this new point
328+
x = self.model.as_absolute_coordinates(xpts_added[k, :])
329+
rvec_list, obj_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
298330

299-
# Handle exit conditions (f < min obj value or maxfun reached)
300-
if exit_info is not None:
301-
if num_samples_run > 0:
302-
self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
303-
x_in_abs_coords=True)
304-
return exit_info # return & quit
331+
# Handle exit conditions (f < min obj value or maxfun reached)
332+
if exit_info is not None:
333+
if num_samples_run > 0:
334+
self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
335+
x_in_abs_coords=True)
336+
return exit_info # return & quit
305337

306-
# Otherwise, add new results (increments model.npt_so_far)
307-
self.model.change_point(k, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
308-
for i in range(1, num_samples_run):
309-
self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
310-
311-
# If k exceeds N+1, then the positions of the k-th and (k-N)-th interpolation
312-
# points may be switched, in order that the function value at the first of them
313-
# contributes to the off-diagonal second derivative terms of the initial quadratic model.
314-
# Note: this works because the steps for (k) and (k-n) points were in the same coordinate direction
315-
if self.n() + 1 <= k < 2 * self.n() + 1:
316-
# Only swap if steps were in different directions AND new pt has lower objective
317-
if stepa * stepb < 0.0 and self.model.objval[k] < self.model.objval[k - self.n()]:
318-
xpts_added[[k, k-self.n()]] = xpts_added[[k-self.n(), k]]
338+
# Otherwise, add new results (increments model.npt_so_far)
339+
self.model.change_point(k, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
340+
for i in range(1, num_samples_run):
341+
self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
342+
343+
# If k exceeds N+1, then the positions of the k-th and (k-N)-th interpolation
344+
# points may be switched, in order that the function value at the first of them
345+
# contributes to the off-diagonal second derivative terms of the initial quadratic model.
346+
# Note: this works because the steps for (k) and (k-n) points were in the same coordinate direction
347+
if self.n() + 1 <= k < 2 * self.n() + 1:
348+
# Only swap if steps were in different directions AND new pt has lower objective
349+
if stepa * stepb < 0.0 and self.model.objval[k] < self.model.objval[k - self.n()]:
350+
xpts_added[[k, k-self.n()]] = xpts_added[[k-self.n(), k]]
319351

320352
return None # return & continue
321353

0 commit comments

Comments
 (0)