def ensure_one_active_gen(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs['in']`` are given. If there is no active generator, then one is started. .. seealso:: `test_fast_alloc.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_fast_alloc.py>`_ # noqa """ Work = {} gen_flag = True for i in avail_worker_ids(W): if persis_info['next_to_give'] < len(H): # Give sim work if possible sim_work(Work, i, sim_specs['in'], [persis_info['next_to_give']], []) persis_info['next_to_give'] += 1 elif not test_any_gen(W) and gen_flag: if not all(H['returned']): break # Give gen work persis_info['total_gen_calls'] += 1 gen_flag = False gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[i]) return Work, persis_info
def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs['in']`` are given. If all entries in `H` have been given a be evaluated, a worker is told to call the generator function, provided this wouldn't result in more than ``alloc_specs['user']['num_active_gen']`` active generators. .. seealso:: `test_fast_alloc.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_fast_alloc.py>`_ # noqa """ Work = {} gen_count = count_gens(W) for i in avail_worker_ids(W): if persis_info['next_to_give'] < len(H): # Give sim work if possible sim_work(Work, i, sim_specs['in'], [persis_info['next_to_give']], []) persis_info['next_to_give'] += 1 elif gen_count < alloc_specs['user'].get('num_active_gens', gen_count + 1): # Give gen work persis_info['total_gen_calls'] += 1 gen_count += 1 gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[i]) return Work, persis_info
def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs['in']`` are given. If all entries in `H` have been given to be evaluated, a worker is told to call the generator function, provided this wouldn't result in more than ``alloc_specs['user']['num_active_gen']`` active generators. Also allows for a ``'batch_mode'``. .. seealso:: `test_old_aposmm_with_gradients.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_old_aposmm_with_gradients.py>`_ # noqa """ Work = {} gen_count = count_gens(W) for i in avail_worker_ids(W): # Find indices of H that are not yet allocated if persis_info['next_to_give'] < len(H): # Give sim work if possible sim_work(Work, i, sim_specs['in'], [persis_info['next_to_give']], []) persis_info['next_to_give'] += 1 elif gen_count < alloc_specs['user'].get('num_active_gens', gen_count + 1): lw = persis_info['last_worker'] last_size = persis_info.get('last_size') if len(H): # Don't give gen instances in batch mode if points are unfinished if (alloc_specs['user'].get('batch_mode') and not all( np.logical_or(H['returned'][last_size:], H['paused'][last_size:]))): break # Don't call APOSMM if there are runs going but none need advancing if len(persis_info[lw]['run_order']): runs_needing_to_advance = np.zeros(len( persis_info[lw]['run_order']), dtype=bool) for run, inds in enumerate( persis_info[lw]['run_order'].values()): runs_needing_to_advance[run] = H['returned'][inds[-1]] if not np.any(runs_needing_to_advance): break persis_info['last_size'] = len(H) # Give gen work persis_info['total_gen_calls'] += 1 gen_count += 1 gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[lw]) persis_info['last_worker'] = i return Work, persis_info
def finite_diff_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function will give simulation work if possible, but otherwise start 1 persistent generator. If all points requested by the persistent generator for a given (x_ind,f_ind) pair have been returned from the simulation evaluation, then this information is given back to the persistent generator (where x_ind is in range(n) and f_ind is in range(p)) .. seealso:: `test_persistent_fd_param_finder.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_persistent_fd_param_finder.py>`_ # noqa """ Work = {} gen_count = count_persis_gens(W) if len(H) and gen_count == 0: # The one persistent worker is done. Exiting return Work, persis_info, 1 # If i is in persistent mode, and all of its calculated values have # returned, give them back to i. Otherwise, give nothing to i for i in avail_worker_ids(W, persistent=True): # What (x_ind, f_ind) pairs have all of the evaluation of all n_ind # values complete. inds_not_sent_back = ~H['given_back'] H_tmp = H[inds_not_sent_back] inds_to_send = np.array([], dtype=int) for x_ind in range(gen_specs['user']['n']): for f_ind in range(gen_specs['user']['p']): inds = np.logical_and.reduce((H_tmp['x_ind'] == x_ind, H_tmp['f_ind'] == f_ind, H_tmp['returned'])) if sum(inds) == gen_specs['user']['nf']: inds_to_send = np.append(inds_to_send, H_tmp['sim_id'][inds]) if len(inds_to_send): gen_work(Work, i, list(set(gen_specs['in'] + sim_specs['in'] + [n[0] for n in sim_specs['out']] + [('sim_id')])), np.atleast_1d(inds_to_send), persis_info[i], persistent=True) H['given_back'][inds_to_send] = True task_avail = ~H['given'] for i in avail_worker_ids(W, persistent=False): if np.any(task_avail): # perform sim evaluations (if they exist in History). sim_ids_to_send = np.nonzero(task_avail)[0][0] # oldest point sim_work(Work, i, sim_specs['in'], np.atleast_1d(sim_ids_to_send), persis_info[i]) task_avail[sim_ids_to_send] = False elif gen_count == 0: # Finally, call a persistent generator as there is nothing else to do. gen_count += 1 gen_work(Work, i, gen_specs['in'], [], persis_info[i], persistent=True) return Work, persis_info, 0
def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function will give simulation work if possible, but otherwise start up to 1 persistent generator. If all points requested by the persistent generator have been returned from the simulation evaluation, then this information is given back to the persistent generator. .. seealso:: `test_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_persistent_uniform_sampling.py>`_ # noqa """ Work = {} gen_count = count_persis_gens(W) if len(H) and gen_count == 0: # The one persistent worker is done. Exiting return Work, persis_info, 1 # If i is in persistent mode, and all of its calculated values have # returned, give them back to i. Otherwise, give nothing to i for i in avail_worker_ids(W, persistent=True): gen_inds = (H['gen_worker'] == i) if np.all(H['returned'][gen_inds]): last_time_gen_gave_batch = np.max(H['gen_time'][gen_inds]) inds_of_last_batch_from_gen = H['sim_id'][gen_inds][ H['gen_time'][gen_inds] == last_time_gen_gave_batch] gen_work(Work, i, sim_specs['in'] + [n[0] for n in sim_specs['out']] + [('sim_id')], np.atleast_1d(inds_of_last_batch_from_gen), persis_info[i], persistent=True) H['given_back'][inds_of_last_batch_from_gen] = True task_avail = ~H['given'] for i in avail_worker_ids(W, persistent=False): if np.any(task_avail): # perform sim evaluations (if they exist in History). sim_ids_to_send = np.nonzero(task_avail)[0][0] # oldest point sim_work(Work, i, sim_specs['in'], np.atleast_1d(sim_ids_to_send), persis_info[i]) task_avail[sim_ids_to_send] = False elif gen_count == 0: # Finally, call a persistent generator as there is nothing else to do. gen_count += 1 gen_work(Work, i, gen_specs['in'], [], persis_info[i], persistent=True) return Work, persis_info, 0
def start_persistent_local_opt_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function will do the following: - Start up a persistent generator that is a local opt run at the first point identified by APOSMM's decide_where_to_start_localopt. Note, it will do this only if at least one worker will be left to perform simulation evaluations. - If multiple starting points are available, the one with smallest function value is chosen. - If no candidate starting points exist, points from existing runs will be evaluated (oldest first). - If no points are left, call the generation function. .. seealso:: `test_uniform_sampling_then_persistent_localopt_runs.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_uniform_sampling_then_persistent_localopt_runs.py>`_ # noqa """ Work = {} gen_count = count_persis_gens(W) task_avail = ~H['given'] # If a persistent localopt run has just finished, use run_order to update H # and then remove other information from persis_info for i in persis_info.keys(): if 'done' in persis_info[i]: H['num_active_runs'][persis_info[i]['run_order']] -= 1 if 'x_opt' in persis_info[i]: opt_ind = np.all(H['x'] == persis_info[i]['x_opt'], axis=1) assert sum(opt_ind) == 1, "There must be just one optimum" H['local_min'][opt_ind] = True persis_info[i] = {'rand_stream': persis_info[i]['rand_stream']} # If i is idle, but in persistent mode, and its calculated values have # returned, give them back to i. Otherwise, give nothing to i for i in avail_worker_ids(W, persistent=True): gen_inds = (H['gen_worker'] == i) if np.all(H['returned'][gen_inds]): last_time_pos = np.argmax(H['given_time'][gen_inds]) last_ind = np.nonzero(gen_inds)[0][last_time_pos] gen_work(Work, i, sim_specs['in'] + [n[0] for n in sim_specs['out']], np.atleast_1d(last_ind), persis_info[i], persistent=True) persis_info[i]['run_order'].append(last_ind) for i in avail_worker_ids(W, persistent=False): # Find candidates to start local opt runs if a sample has been evaluated if np.any(np.logical_and(~H['local_pt'], H['returned'])): n, _, _, _, r_k, mu, nu = initialize_APOSMM(H, gen_specs) update_history_dist(H, n, gen_specs['user'], c_flag=False) starting_inds = decide_where_to_start_localopt(H, r_k, mu, nu) else: starting_inds = [] # Start persistent generator for local opt run unless it would use all workers if starting_inds and gen_count + 1 < len(W): # Start at the best possible starting point ind = starting_inds[np.argmin(H['f'][starting_inds])] gen_work(Work, i, sim_specs['in'] + [n[0] for n in sim_specs['out']], np.atleast_1d(ind), persis_info[i], persistent=True) H['started_run'][ind] = 1 H['num_active_runs'][ind] += 1 persis_info[i]['run_order'] = [ind] gen_count += 1 elif np.any(task_avail): # Perform sim evaluations from existing runs q_inds_logical = np.logical_and(task_avail, H['local_pt']) if not np.any(q_inds_logical): q_inds_logical = task_avail sim_ids_to_send = np.nonzero(q_inds_logical)[0][0] # oldest point sim_work(Work, i, sim_specs['in'], np.atleast_1d(sim_ids_to_send), []) task_avail[sim_ids_to_send] = False elif (gen_count == 0 and not np.any( np.logical_and(W['active'] == EVAL_GEN_TAG, W['persis_state'] == 0))): # Finally, generate points since there is nothing else to do gen_count += 1 gen_work(Work, i, gen_specs['in'], [], persis_info[i]) return Work, persis_info
def only_persistent_gens_for_inverse_bayes(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ Starts up to gen_count number of persistent generators. These persistent generators produce points (x) in batches and subbatches. The points x are given in subbatches to workers to perform a calculation. When all subbatches have returned, their output is given back to the corresponding persistent generator. The first time called there are no persis_w 1st for loop is not done """ Work = {} gen_count = count_persis_gens(W) # If i is idle, but in persistent mode, and generated work has all returned # give output back to i. Otherwise, give nothing to i for i in avail_worker_ids(W, persistent=True): # if > 1 persistant generator, assign the correct work to it inds_generated_by_i = (H['gen_worker'] == i) if np.all(H['returned'][inds_generated_by_i]): # Has sim_f completed everything from this persistent worker? # Then give back everything in the last batch batch_ids = H['batch'][inds_generated_by_i] last_batch_inds = (batch_ids == np.max(batch_ids)) inds_to_send_back = np.where( np.logical_and(inds_generated_by_i, last_batch_inds))[0] if H['batch'][-1] > 0: n = gen_specs['user']['subbatch_size'] * gen_specs['user'][ 'num_subbatches'] k = H['batch'][-1] H['weight'][(n * (k - 1)):(n * k)] = H['weight'][(n * k):(n * (k + 1))] gen_work(Work, i, ['like'], np.atleast_1d(inds_to_send_back), persis_info[i], persistent=True) task_avail = ~H['given'] for i in avail_worker_ids(W, persistent=False): if np.any(task_avail): # perform sim evaluations (if any point hasn't been given). sim_subbatches = H['subbatch'][task_avail] sim_inds = (sim_subbatches == np.min(sim_subbatches)) sim_ids_to_send = np.nonzero(task_avail)[0][sim_inds] sim_work(Work, i, sim_specs['in'], np.atleast_1d(sim_ids_to_send), []) task_avail[sim_ids_to_send] = False elif gen_count == 0: # Finally, generate points since there is nothing else to do. gen_count += 1 gen_work(Work, i, gen_specs['in'], [], persis_info[i], persistent=True) return Work, persis_info
def persistent_aposmm_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function will give simulation work if possible, but otherwise start a persistent APOSMM generator. If all points requested by the persistent generator have been returned from the simulation evaluation, then this information is given back to the persistent generator. This function assumes that one persistent APOSMM will be started and never stopped (until some exit_criterion is satisfied). .. seealso:: `test_persistent_aposmm_with_grad.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_persistent_aposmm_with_grad.py>`_ # noqa """ Work = {} if persis_info.get('first_call', True): assert np.all(H['given']), "Initial points in H have never been given." assert np.all(H['given_back']), "Initial points in H have never been given_back." assert np.all(H['returned']), "Initial points in H have never been returned." persis_info['fields_to_give_back'] = ['f'] + [n[0] for n in gen_specs['out']] if 'grad' in [n[0] for n in sim_specs['out']]: persis_info['fields_to_give_back'] += ['grad'] if 'fvec' in [n[0] for n in sim_specs['out']]: persis_info['fields_to_give_back'] += ['fvec'] persis_info['samples_in_H0'] = sum(H['local_pt'] == 0) persis_info['next_to_give'] = len(H) # persis_info['first_call'] = False # If any persistent worker's calculated values have returned, give them back. for i in avail_worker_ids(W, persistent=True): if (persis_info.get('sample_done') or sum(H['returned']) >= gen_specs['user']['initial_sample_size'] + persis_info['samples_in_H0']): # Don't return if the initial sample is not complete persis_info['sample_done'] = True returned_but_not_given = np.logical_and(H['returned'], ~H['given_back']) if np.any(returned_but_not_given): inds_to_give = np.where(returned_but_not_given)[0] gen_work(Work, i, persis_info['fields_to_give_back'], np.atleast_1d(inds_to_give), persis_info[i], persistent=True) H['given_back'][inds_to_give] = True for i in avail_worker_ids(W, persistent=False): if persis_info['next_to_give'] < len(H): # perform sim evaluations (if they exist in History). sim_work(Work, i, sim_specs['in'], np.atleast_1d(persis_info['next_to_give']), persis_info[i]) persis_info['next_to_give'] += 1 elif persis_info.get('gen_started') is None: # Finally, call a persistent generator as there is nothing else to do. persis_info['gen_started'] = True persis_info[i]['nworkers'] = len(W) gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[i], persistent=True) return Work, persis_info
def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs['in']`` are given. If all entries in `H` have been given a be evaluated, a worker is told to call the generator function, provided this wouldn't result in more than ``alloc_specs['user']['num_active_gen']`` active generators. Also allows for a 'batch_mode'. When there are multiple objective components, this allocation function does not evaluate further components for some point in the following scenarios: alloc_specs['user']['stop_on_NaNs']: True --- after a NaN has been found in returned in some objective component alloc_specs['user']['stop_partial_fvec_eval']: True --- after the value returned from combine_component_func is larger than a known upper bound on the objective. .. seealso:: `test_uniform_sampling_one_residual_at_a_time.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_uniform_sampling_one_residual_at_a_time.py>`_ # noqa """ Work = {} gen_count = count_gens(W) if gen_specs['user'].get('single_component_at_a_time'): assert alloc_specs['user']['batch_mode'], ( "Must be in batch mode when using " "'single_component_at_a_time'") if len(H) != persis_info['H_len']: # Something new is in the history. persis_info['need_to_give'].update( H['sim_id'][persis_info['H_len']:].tolist()) persis_info['H_len'] = len(H) persis_info['pt_ids'] = set(np.unique(H['pt_id'])) for pt_id in persis_info['pt_ids']: persis_info['inds_of_pt_ids'][pt_id] = H['pt_id'] == pt_id idle_workers = avail_worker_ids(W) while len(idle_workers): pt_ids_to_pause = set() # Find indices of H that are not yet given out to be evaluated if len(persis_info['need_to_give']): # If 'stop_on_NaN' is true and any f_i is a NaN, then pause # evaluations of other f_i, corresponding to the same pt_id if alloc_specs['user'].get('stop_on_NaNs'): pt_ids_to_pause.update(H['pt_id'][np.isnan(H['f_i'])]) # If 'stop_partial_fvec_eval' is true, pause entries in H if a # partial combine_component_func evaluation is # worse than the # best, known, complete evaluation (and the point is not a # local_pt). if alloc_specs['user'].get('stop_partial_fvec_eval'): pt_ids = set( persis_info['pt_ids'] ) - persis_info['has_nan'] - persis_info['complete'] pt_ids = np.array(list(pt_ids)) partial_fvals = np.zeros(len(pt_ids)) # Mark 'complete' and 'has_nan' pt_ids, compute complete and partial fvals for j, pt_id in enumerate(pt_ids): a1 = persis_info['inds_of_pt_ids'][pt_id] if np.any(np.isnan(H['f_i'][a1])): persis_info['has_nan'].add(pt_id) continue if 'local_pt' in H.dtype.names and H['local_pt'][a1][0]: persis_info['local_pt_ids'].add(pt_id) if np.all(H['returned'][a1]): persis_info['complete'].add(pt_id) values = gen_specs['user']['combine_component_func']( H['f_i'][a1]) persis_info['best_complete_val'] = min( persis_info['best_complete_val'], values) else: # Ensure combine_component_func calculates partial fevals correctly # with H['f_i'] = 0 for non-returned point partial_fvals[j] = gen_specs['user'][ 'combine_component_func'](H['f_i'][a1]) if len(persis_info['complete']) and len(pt_ids) > 1: worse_flag = np.zeros(len(pt_ids), dtype=bool) for j, pt_id in enumerate((pt_ids)): if (not np.isnan(partial_fvals[j])) and \ (pt_id not in persis_info['local_pt_ids']) and \ (pt_id not in persis_info['complete']) and \ (partial_fvals[j] > persis_info['best_complete_val']): worse_flag[j] = True # Pause incompete evaluations with worse_flag==True pt_ids_to_pause.update(pt_ids[worse_flag]) if not pt_ids_to_pause.issubset(persis_info['already_paused']): persis_info['already_paused'].update(pt_ids_to_pause) sim_ids_to_remove = np.in1d(H['pt_id'], list(pt_ids_to_pause)) H['paused'][sim_ids_to_remove] = True persis_info['need_to_give'] = persis_info[ 'need_to_give'].difference(np.where(sim_ids_to_remove)[0]) if len(persis_info['need_to_give']) != 0: next_row = persis_info['need_to_give'].pop() i, idle_workers = idle_workers[0], idle_workers[1:] sim_work(Work, i, sim_specs['in'], [next_row], []) elif gen_count < alloc_specs['user'].get('num_active_gens', gen_count + 1): lw = persis_info['last_worker'] last_size = persis_info.get('last_size') if len(H): # Don't give gen instances in batch mode if points are unfinished if (alloc_specs['user'].get('batch_mode') and not all( np.logical_or(H['returned'][last_size:], H['paused'][last_size:]))): break # Don't call APOSMM if there are runs going but none need advancing if len(persis_info[lw]['run_order']): runs_needing_to_advance = np.zeros(len( persis_info[lw]['run_order']), dtype=bool) for run, inds in enumerate( persis_info[lw]['run_order'].values()): runs_needing_to_advance[run] = np.all( H['returned'][inds]) if not np.any(runs_needing_to_advance): break persis_info['last_size'] = len(H) # Give gen work persis_info['total_gen_calls'] += 1 gen_count += 1 i, idle_workers = idle_workers[0], idle_workers[1:] gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[lw]) persis_info['last_worker'] = i elif gen_count >= alloc_specs['user'].get('num_active_gens', gen_count + 1): idle_workers = [] return Work, persis_info
def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info): """ Decide what should be given to workers. This allocation function gives any available simulation work first, and only when all simulations are completed or running does it start (at most ``alloc_specs['user']['num_active_gens']``) generator instances. Allows for a ``alloc_specs['user']['batch_mode']`` where no generation work is given out unless all entries in ``H`` are returned. Allows for ``blocking`` of workers that are not active, for example, so their resources can be used for a different simulation evaluation. Can give points in highest priority, if ``'priority'`` is a field in ``H``. This is the default allocation function if one is not defined. .. seealso:: `test_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_uniform_sampling.py>`_ # noqa """ Work = {} gen_count = count_gens(W) avail_set = set(W['worker_id'][np.logical_and(~W['blocked'], W['active'] == 0)]) for i in avail_worker_ids(W): if i not in avail_set: pass elif not np.all(H['allocated']): # Pick all high priority, oldest high priority, or just oldest point if 'priority' in H.dtype.fields: priorities = H['priority'][~H['allocated']] if gen_specs['user'].get('give_all_with_same_priority'): q_inds = (priorities == np.max(priorities)) else: q_inds = np.argmax(priorities) else: q_inds = 0 # Get sim ids and check resources needed sim_ids_to_send = np.nonzero(~H['allocated'])[0][q_inds] sim_ids_to_send = np.atleast_1d(sim_ids_to_send) nodes_needed = (np.max(H[sim_ids_to_send]['num_nodes']) if 'num_nodes' in H.dtype.names else 1) if nodes_needed > len(avail_set): break # Assign resources and mark tasks as allocated to workers sim_work(Work, i, sim_specs['in'], sim_ids_to_send, persis_info[i]) H['allocated'][sim_ids_to_send] = True # Update resource records avail_set.remove(i) if nodes_needed > 1: workers_to_block = list(avail_set)[:nodes_needed - 1] avail_set.difference_update(workers_to_block) Work[i]['libE_info']['blocking'] = workers_to_block else: # Allow at most num_active_gens active generator instances if gen_count >= alloc_specs['user'].get('num_active_gens', gen_count + 1): break # No gen instances in batch mode if workers still working still_working = ~H['returned'] if alloc_specs['user'].get('batch_mode') and np.any(still_working): break # Give gen work gen_count += 1 if 'in' in gen_specs and len(gen_specs['in']): gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[i]) else: gen_work(Work, i, [], [], persis_info[i]) return Work, persis_info