def evaluate_all(sim, env, pop, print_log, save_vxa_every, run_directory, run_name, max_eval_time=120,
                 time_to_try_again=10, save_lineages=False):
    """Evaluate all individuals of the population in VoxCad.

    Parameters
    ----------
    sim : Sim
        Configures parameters of the Voxelyze simulation.

    env : Env
        Configures parameters of the Voxelyze environment.

    pop : Population
        This provides the individuals to evaluate.

    print_log : PrintLog()
        For logging with time stamps

    save_vxa_every : int
        Which generations to save information about individual SoftBots

    run_directory : string
        Where to save

    run_name : string
        Experiment name for files

    max_eval_time : int
        How long to run physical simulation per ind in pop

    time_to_try_again : int
        How long to wait until relaunching remaining unevaluated (crashed?) simulations

    save_lineages : bool
        Save the vxa of every ancestor of the surviving individual

    """
    start_time = time.time()
    num_evaluated_this_gen = 0
    ids_to_analyze = []

    controller_evolution = hasattr(pop[0].genotype, "controller")

    for ind in pop:

        # set environmental parameters defined in the controller
        if controller_evolution:
            controller = ind.genotype.controller
            env.temp_amp = env.temp_base + controller.temp_amplitude
            env.period = controller.temp_period
            env.cte = controller.muscles_cte

        # insert individual in the environment if obstacles have been enabled
        if env.obstacles:
            for name, details in ind.genotype.to_phenotype_mapping.items():
                if details["env_kws"] is None:
                    env.insert_individual(details)

        # write the phenotype of a SoftBot to a file so that VoxCad can access for sim.
        ind.md5 = write_voxelyze_file(sim, env, ind, run_directory, run_name)

        # don't evaluate if invalid
        if not ind.phenotype.is_valid():
            for rank, goal in pop.objective_dict.items():
                if goal["name"] != "age":
                    setattr(ind, goal["name"], goal["worst_value"])
            print_log.message("Skipping invalid individual")

        # don't evaluate if identical phenotype has already been evaluated
        elif env.actuation_variance == 0 and ind.md5 in pop.already_evaluated:
            for rank, goal in pop.objective_dict.items():
                if goal["tag"] is not None:
                    setattr(ind, goal["name"], pop.already_evaluated[ind.md5][rank])
            # print_log.message("Individual already evaluated:  cached fitness is {}".format(ind.fitness))

            if pop.gen % save_vxa_every == 0 and save_vxa_every > 0:
                sub.call("cp " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" % ind.id +
                         " " + run_directory + "/Gen_%04i/" % pop.gen + run_name +
                         "--Gen_%04i--fit_%.08f--id_%05i.vxa" % (pop.gen, ind.fitness, ind.id), shell=True)

        # otherwise evaluate with voxelyze
        else:
            num_evaluated_this_gen += 1
            pop.total_evaluations += 1
            ids_to_analyze += [ind.id]

            sub.Popen("./voxelyze  -f " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" % ind.id,
                      shell=True)

    print_log.message("Launched {0} voxelyze calls, out of {1} individuals".format(num_evaluated_this_gen, len(pop)))

    num_evals_finished = 0
    all_done = False
    already_analyzed_ids = []
    redo_attempts = 1

    fitness_eval_start_time = time.time()

    while not all_done:

        time_waiting_for_fitness = time.time() - fitness_eval_start_time
        # this protects against getting stuck when voxelyze doesn't return a fitness value
        # (diverging simulations, crash, error reading .vxa)

        if time_waiting_for_fitness > pop.pop_size * max_eval_time:
            # TODO ** WARNING: This could in fact alter the sim and undermine the reproducibility **
            all_done = False  # something bad with this individual, probably sim diverged
            break

        if time_waiting_for_fitness > pop.pop_size * time_to_try_again * redo_attempts:
            # try to redo any simulations that crashed
            redo_attempts += 1
            non_analyzed_ids = [idx for idx in ids_to_analyze if idx not in already_analyzed_ids]
            print "Rerunning voxelyze for: ", non_analyzed_ids
            for idx in non_analyzed_ids:
                sub.Popen("./voxelyze  -f " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" % idx,
                          shell=True)

        # check to see if all are finished
        all_done = True
        for ind in pop:
            if ind.phenotype.is_valid() and ind.fitness == pop.objective_dict[0]["worst_value"]:
                all_done = False

        # check for any fitness files that are present
        ls_check = sub.check_output(["ls", run_directory + "/fitnessFiles/"])
        # duplicated ids issue: may be due to entering here two times for the same fitness file found in the directory.

        if ls_check:
            # ls_check = random.choice(ls_check.split())  # doesn't accomplish anything and undermines reproducibility
            ls_check = ls_check.split()[0]
            if "softbotsOutput--id_" in ls_check:
                this_id = int(ls_check.split("_")[1].split(".")[0])

                if this_id in already_analyzed_ids:
                    # workaround to avoid any duplicated ids when restarting sims
                    print_log.message("Duplicate voxelyze results found from THIS gen with id {}".format(this_id))
                    sub.call("rm " + run_directory + "/fitnessFiles/" + ls_check, shell=True)

                elif this_id in pop.all_evaluated_individuals_ids:
                    print_log.message("Duplicate voxelyze results found from PREVIOUS gen with id {}".format(this_id))
                    sub.call("rm " + run_directory + "/fitnessFiles/" + ls_check, shell=True)

                else:
                    num_evals_finished += 1
                    already_analyzed_ids.append(this_id)

                    ind_filename = run_directory + "/fitnessFiles/" + ls_check

                    objective_values_dict = read_voxlyze_results(pop, print_log, ind_filename)
                    if env.novelty_based:
                        centroids = read_voxelyze_centroids(pop, print_log, ind_filename)

                    print_log.message("{0} fit = {1} ({2} / {3})".format(ls_check, objective_values_dict[0],
                                                                         num_evals_finished,
                                                                         num_evaluated_this_gen))

                    # now that we've read the fitness file, we can remove it
                    sub.call("rm " + run_directory + "/fitnessFiles/" + ls_check, shell=True)

                    # assign the values to the corresponding individual
                    for ind in pop:
                        if ind.id == this_id:
                            for rank, details in pop.objective_dict.items():
                                if objective_values_dict[rank] is not None:
                                    setattr(ind, details["name"], objective_values_dict[rank])
                                    if env.novelty_based:
                                        setattr(ind, "trajectory", from_centroids_to_trajectory(centroids))
                                else:
                                    # for network in ind.genotype:
                                    #     for name in network.output_node_names:
                                    #         if name == details["output_node_name"]:
                                    #             print "here!"
                                    #             # apply the specified function to the specified output node
                                    #             state = network.graph.node[name]["state"]
                                    #             setattr(ind, details["name"], details["node_func"](state))
                                    for name, details_phenotype in ind.genotype.to_phenotype_mapping.items():
                                        if name == details["output_node_name"]:
                                            state = details_phenotype["state"]
                                            setattr(ind, details["name"], details["node_func"](state))

                            pop.already_evaluated[ind.md5] = [getattr(ind, details["name"])
                                                              for rank, details in
                                                              pop.objective_dict.items()]
                            pop.all_evaluated_individuals_ids += [this_id]

                            # update the run statistics and file management
                            if ind.fitness > pop.best_fit_so_far:
                                pop.best_fit_so_far = ind.fitness
                                sub.call("cp " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" %
                                         ind.id + " " + run_directory + "/bestSoFar/fitOnly/" + run_name +
                                         "--Gen_%04i--fit_%.08f--id_%05i.vxa" %
                                         (pop.gen, ind.fitness, ind.id), shell=True)

                            if save_lineages:
                                sub.call("cp " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" %
                                         ind.id + " " + run_directory + "/ancestors/", shell=True)

                            if pop.gen % save_vxa_every == 0 and save_vxa_every > 0:
                                sub.call("mv " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" %
                                         ind.id + " " + run_directory + "/Gen_%04i/" % pop.gen +
                                         run_name + "--Gen_%04i--fit_%.08f--id_%05i.vxa" %
                                         (pop.gen, ind.fitness, ind.id), shell=True)
                            else:
                                sub.call("rm " + run_directory + "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" %
                                         ind.id, shell=True)

                            break

            # wait a second and try again
            else:
                time.sleep(0.5)
        else:
            time.sleep(0.5)

    if not all_done:
        print_log.message("WARNING: Couldn't get a fitness value in time for some individuals. "
                          "The min fitness was assigned for these individuals")

    print_log.message("\nAll Voxelyze evals finished in {} seconds".format(time.time() - start_time))
    print_log.message("num_evaluated_this_gen: {0}".format(num_evaluated_this_gen))
    print_log.message("total_evaluations: {}".format(pop.total_evaluations))
Пример #2
0
def evaluate_all(sim,
                 env,
                 pop,
                 print_log,
                 save_vxa_every,
                 run_directory,
                 run_name,
                 max_eval_time=60,
                 time_to_try_again=10,
                 save_lineages=False,
                 batch_size=None):
    """Evaluate all individuals of the population in VoxCad.

    Parameters
    ----------
    sim : Sim
        Configures parameters of the Voxelyze simulation.

    env : Env
        Configures parameters of the Voxelyze environment.

    pop : Population
        This provides the individuals to evaluate.

    print_log : PrintLog()
        For logging with time stamps

    save_vxa_every : int
        Which generations to save information about individual SoftBots

    run_directory : string
        Where to save

    run_name : string
        Experiment name for files

    max_eval_time : int
        How long to run physical simulation per ind in pop

    time_to_try_again : int
        How long to wait until relaunching remaining unevaluated (crashed?) simulations

    save_lineages : bool
        Save the vxa of every ancestor of the surviving individual

    batch_size : int
        How many voxelyze processes start at once

    """
    start_time = time.time()
    num_evaluated_this_gen = 0
    ids_to_analyze = []

    for n, ind in enumerate(pop):

        # write the phenotype of a SoftBot to a file so that VoxCad can access for sim.
        ind.md5 = write_voxelyze_file(sim, env, ind, run_directory, run_name)

        # don't evaluate if invalid
        if not ind.phenotype.is_valid():
            for rank, goal in pop.objective_dict.items():
                if goal["name"] != "age":
                    setattr(ind, goal["name"], goal["worst_value"])
            print_log.message("Skipping invalid individual")

        # don't evaluate if identical phenotype has already been evaluated
        elif env.actuation_variance == 0 and ind.md5 in pop.already_evaluated:

            if not ind.previously_aggregated:  # don't overwrite the fitness for aggregations of multiple trials

                for rank, goal in pop.objective_dict.items():
                    if goal["tag"] is not None:
                        setattr(ind, goal["name"],
                                pop.already_evaluated[ind.md5][rank])
                # print_log.message("Individual already evaluated:  cached fitness is {}".format(ind.fitness))

            if pop.gen % save_vxa_every == 0 and save_vxa_every > 0:
                sub.call("cp " + run_directory + "/voxelyzeFiles/" + run_name +
                         "--id_%05i.vxa" % ind.id + " " + run_directory +
                         "/Gen_%04i/" % pop.gen + run_name +
                         "--Gen_%04i--fit_%.08f--id_%05i.vxa" %
                         (pop.gen, ind.fitness, ind.id),
                         shell=True)

        # ##############################################################################################################
        # # todo: there might be cases (generalize this!) in which we don't want to waste time evaluating in simulation
        # elif pop.learning_trials > 0 and \
        #         get_depths_of_material_from_shell(
        #             ind.genotype.to_phenotype_mapping['material']['state'], mat=8)[4] > 0:
        #     for rank, goal in pop.objective_dict.items():
        #         if goal["name"] != "age":
        #             setattr(ind, goal["name"], goal["default_value"])
        # ##############################################################################################################

        # otherwise evaluate with voxelyze
        else:
            num_evaluated_this_gen += 1
            pop.total_evaluations += 1
            ids_to_analyze += [ind.id]

            sub.Popen("./voxelyze  -f " + run_directory + "/voxelyzeFiles/" +
                      run_name + "--id_%05i.vxa" % ind.id,
                      shell=True)

        # # pause if using mini-batches
        # batch_start_time = time.time()
        # if batch_size < pop.pop_size:
        #     if (n+1) % batch_size == 0:
        #         done_with_batch = False
        #         while not done_with_batch:
        #             ls_check = sub.check_output(["ls", run_directory + "/fitnessFiles/"])
        #             if len(ls_check.split()) == n+1:
        #                 done_with_batch = True
        #
        #             elif time.time() - batch_start_time > time_to_try_again*batch_size:
        #                 done_with_batch = True  # resend this robot later
        #
        #             else:
        #                 time.sleep(0.5)

    print_log.message(
        "Launched {0} voxelyze calls, out of {1} individuals".format(
            num_evaluated_this_gen, len(pop)))

    num_evals_finished = 0
    all_done = False
    already_analyzed_ids = []
    redo_attempts = 1

    fitness_eval_start_time = time.time()

    if num_evaluated_this_gen == 0:
        all_done = True

    # if batch_size < pop.pop_size:
    #     fitness_eval_start_time = batch_start_time  # use timer from just before batch started

    while not all_done:

        time_waiting_for_fitness = time.time() - fitness_eval_start_time
        # this protects against getting stuck when voxelyze doesn't return a fitness value
        # (diverging simulations, crash, error reading .vxa)

        if time_waiting_for_fitness > pop.pop_size * max_eval_time:
            # TODO ** WARNING: This could in fact alter the sim and undermine the reproducibility **
            all_done = False  # something bad with this individual, probably sim diverged
            break

        if time_waiting_for_fitness > pop.pop_size * time_to_try_again * redo_attempts:
            # try to redo any simulations that crashed
            redo_attempts += 1
            non_analyzed_ids = [
                idx for idx in ids_to_analyze
                if idx not in already_analyzed_ids
            ]
            print "Rerunning voxelyze for: ", non_analyzed_ids
            for idx in non_analyzed_ids:
                sub.Popen("./voxelyze  -f " + run_directory +
                          "/voxelyzeFiles/" + run_name + "--id_%05i.vxa" % idx,
                          shell=True)

        # check to see if all are finished
        all_done = True
        for ind in pop:
            if ind.phenotype.is_valid(
            ) and ind.fitness == pop.objective_dict[0]["worst_value"]:
                all_done = False

        # check for any fitness files that are present
        ls_check = sub.check_output(["ls", run_directory + "/fitnessFiles/"])
        # duplicated ids issue: may be due to entering here two times for the same fitness file found in the directory.

        if ls_check:
            # ls_check = random.choice(ls_check.split())  # doesn't accomplish anything and undermines reproducibility
            ls_check = ls_check.split()[0]
            if "softbotsOutput--id_" in ls_check:
                this_id = int(ls_check.split("_")[1].split(".")[0])

                if this_id in already_analyzed_ids:
                    # workaround to avoid any duplicated ids when restarting sims
                    print_log.message(
                        "Duplicate voxelyze results found from THIS gen with id {}"
                        .format(this_id))
                    sub.call("rm " + run_directory + "/fitnessFiles/" +
                             ls_check,
                             shell=True)

                elif this_id in pop.all_evaluated_individuals_ids and pop.learning_trials <= 1:
                    print_log.message(
                        "Duplicate voxelyze results found from PREVIOUS gen with id {}"
                        .format(this_id))
                    sub.call("rm " + run_directory + "/fitnessFiles/" +
                             ls_check,
                             shell=True)

                else:
                    num_evals_finished += 1
                    already_analyzed_ids.append(this_id)

                    ind_filename = run_directory + "/fitnessFiles/" + ls_check
                    objective_values_dict = read_voxlyze_results(
                        pop, print_log, ind_filename)

                    print_log.message("{0} fit = {1} ({2} / {3})".format(
                        ls_check, objective_values_dict[0], num_evals_finished,
                        num_evaluated_this_gen))

                    # now that we've read the fitness file, we can remove it
                    sub.call("rm " + run_directory + "/fitnessFiles/" +
                             ls_check,
                             shell=True)

                    # assign the values to the corresponding individual
                    for ind in pop:
                        if ind.id == this_id:
                            for rank, details in pop.objective_dict.items():
                                if objective_values_dict[rank] is not None:
                                    this_value = objective_values_dict[rank]
                                    if details["meta_func"] is not None:
                                        this_value = details["meta_func"](
                                            this_value, ind)
                                    setattr(ind, details["name"], this_value)
                                else:
                                    # for network in ind.genotype:
                                    #     for name in network.output_node_names:
                                    #         if name == details["output_node_name"]:
                                    #             print "here!"
                                    #             # apply the specified function to the specified output node
                                    #             state = network.graph.node[name]["state"]
                                    #             setattr(ind, details["name"], details["node_func"](state))
                                    for name, details_phenotype in ind.genotype.to_phenotype_mapping.items(
                                    ):
                                        if name == details["output_node_name"]:
                                            state = details_phenotype["state"]
                                            setattr(
                                                ind, details["name"],
                                                details["node_func"](state))

                            pop.already_evaluated[ind.md5] = [
                                getattr(ind, details["name"]) for rank, details
                                in pop.objective_dict.items()
                            ]
                            pop.all_evaluated_individuals_ids += [this_id]

                            # update the run statistics and file management
                            new_run_champ = False
                            if pop.objective_dict[0]["maximize"]:
                                if ind.fitness > pop.best_fit_so_far:
                                    new_run_champ = True
                            elif ind.fitness < pop.best_fit_so_far:
                                new_run_champ = True

                            if new_run_champ:
                                pop.best_fit_so_far = ind.fitness
                                sub.call(
                                    "cp " + run_directory + "/voxelyzeFiles/" +
                                    run_name + "--id_%05i.vxa" % ind.id + " " +
                                    run_directory + "/bestSoFar/fitOnly/" +
                                    run_name +
                                    "--Gen_%04i--fit_%.08f--id_%05i.vxa" %
                                    (pop.gen, ind.fitness, ind.id),
                                    shell=True)

                            if save_lineages:
                                sub.call("cp " + run_directory +
                                         "/voxelyzeFiles/" + run_name +
                                         "--id_%05i.vxa" % ind.id + " " +
                                         run_directory + "/ancestors/",
                                         shell=True)

                            if pop.gen % save_vxa_every == 0 and save_vxa_every > 0:
                                sub.call(
                                    "mv " + run_directory + "/voxelyzeFiles/" +
                                    run_name + "--id_%05i.vxa" % ind.id + " " +
                                    run_directory + "/Gen_%04i/" % pop.gen +
                                    run_name +
                                    "--Gen_%04i--fit_%.08f--id_%05i.vxa" %
                                    (pop.gen, ind.fitness, ind.id),
                                    shell=True)
                            else:
                                sub.call("rm " + run_directory +
                                         "/voxelyzeFiles/" + run_name +
                                         "--id_%05i.vxa" % ind.id,
                                         shell=True)

                            break

            # wait a second and try again
            else:
                time.sleep(0.5)
        else:
            time.sleep(0.5)

    if not all_done:
        print_log.message(
            "WARNING: Couldn't get a fitness value in time for some individuals. "
            "The min fitness was assigned for these individuals")

    print_log.message(
        "\nAll Voxelyze evals finished in {} seconds".format(time.time() -
                                                             start_time))
    print_log.message(
        "num_evaluated_this_gen: {0}".format(num_evaluated_this_gen))
    print_log.message("total_evaluations: {}".format(pop.total_evaluations))