コード例 #1
0
ファイル: fast_alloc.py プロジェクト: Kardyne/libensemble
def give_sim_work_first(W, H, sim_specs, gen_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 ``gen_specs['num_active_gen']`` active generators.

    :See:
        ``/libensemble/tests/regression_tests/test_fast_alloc.py``
    """

    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 < gen_specs.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'], persis_info[i], [])

    return Work, persis_info
コード例 #2
0
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_6-hump_camel_persistent_aposmm.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_6-hump_camel_persistent_aposmm.py>`_
    """

    Work = {}
    if 'next_to_give' not in persis_info:
        persis_info['next_to_give'] = 0

    # 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']:
            # 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, [n[0] for n in sim_specs['out']] +
                         [n[0] for n in gen_specs['out']],
                         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

            gen_work(Work,
                     i,
                     gen_specs['in'], [],
                     persis_info[i],
                     persistent=True)

    return Work, persis_info
コード例 #3
0
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 ``gen_specs['user']['num_active_gen']`` active generators. Also allows
    for a 'batch_mode'.

    .. seealso::
        `test_6-hump_camel_aposmm_LD_MMA.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_6-hump_camel_aposmm_LD_MMA.py>`_
    """

    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 < gen_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
コード例 #4
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_6-hump_camel_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_6-hump_camel_persistent_uniform_sampling.py>`_
    """

    Work = {}
    gen_count = count_persis_gens(W)

    # If i is in persistent mode, and any 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.any(
                np.logical_and(H['returned'][gen_inds],
                               ~H['given_back'][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
コード例 #5
0
def give_sim_work_first(W, H, sim_specs, gen_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 ``gen_specs['num_active_gen']`` active generators. Also allows
    for a 'batch_mode'.

    :See:
        ``/libensemble/tests/regression_tests/test_6-hump_camel_aposmm_LD_MMA.py``
    """

    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 < gen_specs.get('num_active_gens', gen_count + 1):

            # Don't give gen instances in batch mode if points are unfinished
            last_size = persis_info.get('last_size')
            if (gen_specs.get('batch_mode') and len(H) and not all(
                    np.logical_or(H['returned'][last_size:],
                                  H['paused'][last_size:]))):
                break
            else:
                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'], persis_info[i], range(len(H)))

    return Work, persis_info
コード例 #6
0
def only_persistent_gens(W, H, sim_specs, gen_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.

    :See:
        ``/libensemble/tests/regression_tests/test_6-hump_camel_persistent_uniform_sampling.py``
    """

    Work = {}
    gen_count = count_persis_gens(W)

    # 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']],
                     persis_info[i], np.atleast_1d(last_ind), persistent=True)

    task_avail = ~H['given']
    for i in avail_worker_ids(W, persistent=False):
        if np.any(task_avail):

            # perform sim evaluations from existing runs (if they exist).
            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))
            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
コード例 #7
0
def give_pregenerated_sim_work(W, H, sim_specs, gen_specs, alloc_specs,
                               persis_info):
    """
    This allocation function gives (in order) entries in alloc_spec['x'] to
    idle workers. It is an example use case where no gen_func is used.

    .. seealso::
        `test_fast_alloc.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_fast_alloc.py>`_
    """

    Work = {}
    if not persis_info:
        persis_info['next_to_give'] = 0

    assert persis_info['next_to_give'] < len(
        H), 'No more work to give inside give_pregenerated_sim_work.'

    for i in avail_worker_ids(W):
        # Give sim work
        sim_work(Work, i, sim_specs['in'], [persis_info['next_to_give']], [])
        persis_info['next_to_give'] += 1

    return Work, persis_info
コード例 #8
0
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 ``gen_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_chwirut_uniform_sampling_one_residual_at_a_time.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_chwirut_uniform_sampling_one_residual_at_a_time.py>`_
    """

    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)
                        persis_info['best_complete_val'] = min(
                            persis_info['best_complete_val'],
                            gen_specs['user']['combine_component_func'](
                                H['f_i'][a1]))
                    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 < gen_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 >= gen_specs['user'].get('num_active_gens',
                                                gen_count + 1):
            idle_workers = []

    return Work, persis_info
コード例 #9
0
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 ``gen_specs['user']['num_active_gens']``)
    generator instances.

    Allows for a ``gen_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_6-hump_camel_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/regression_tests/test_6-hump_camel_uniform_sampling.py>`_
    """

    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 >= gen_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
コード例 #10
0
def start_persistent_local_opt_gens(W, H, sim_specs, gen_specs, persis_info):
    """
    This allocation function will:

    - Start up a persistent generator that is a local opt run at the first point
      identified by APOSMM's decide_where_to_start_localopt.
    - It will only do this 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.

    :See:
        ``/libensemble/tests/regression_tests/test_6-hump_camel_uniform_sampling_with_persistent_localopt_gens.py``
    """

    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']],
                     persis_info[i],
                     np.atleast_1d(last_ind),
                     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_s, _, _, rk_const, lhs_divisions, mu, nu = initialize_APOSMM(
                H, gen_specs)
            update_history_dist(H, gen_specs, c_flag=False)
            starting_inds = decide_where_to_start_localopt(
                H, n_s, rk_const, lhs_divisions, 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']],
                     persis_info[i],
                     np.atleast_1d(ind),
                     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
コード例 #11
0
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