Source code for machetli.search

import logging
from pathlib import Path

from machetli.environments import LocalEnvironment, EvaluationTask
from machetli.errors import SubmissionError, PollingError
from machetli.successors import make_single_successor_generator
from machetli.tools import batched, configure_logging




def _evaluate_initial_state(evaluator_path, environment, deterministic):
    logging.info("Trying to reproduce the behavior in the initial state.")
    task = environment.evaluate_initial_state(evaluator_path)
    if task.status == EvaluationTask.DONE_AND_BEHAVIOR_NOT_PRESENT:
        logging.warning("Could not reproduce the behavior in the initial state. "
                        "Please check your evaluator script.")
    elif task.status == EvaluationTask.OUT_OF_RESOURCES:
        logging.warning("Could not reproduce the behavior in the initial state "
                        "because the evaluation ran out of resources.")
    elif task.status == EvaluationTask.CRITICAL:
        logging.warning("Could not reproduce the behavior in the initial state "
                        "because the evaluator script crashed with a critical error.")
    else:
        assert task.status == EvaluationTask.DONE_AND_BEHAVIOR_PRESENT
        logging.info("Confirmed that the behavior is present in the initial state.")


def _get_improving_successor(evaluator_path, successors, environment, deterministic):
    tasks_out_of_resources = set()
    for batch in batched(successors, environment.batch_size):
        task_ids = list(range(len(batch)))
        def on_task_completed(task):
            if (deterministic and task.status !=
                    EvaluationTask.DONE_AND_BEHAVIOR_NOT_PRESENT):
                # Either we have an improving successor, or there was an error.
                # In both cases deterministic mode cannot continue.
                task_ids_to_cancel = [i for i in task_ids if i > task.successor_id]
            elif (not deterministic and task.status ==
                  EvaluationTask.DONE_AND_BEHAVIOR_PRESENT):
                # We found an improving successor, so all other evaluations can
                # be canceled.
                task_ids_to_cancel = task_ids
            else:
                task_ids_to_cancel = None
            return task_ids_to_cancel

        tasks = environment.run(evaluator_path, batch, on_task_completed)
        for task in tasks:
            if task.status == EvaluationTask.DONE_AND_BEHAVIOR_NOT_PRESENT:
                continue
            elif task.status == EvaluationTask.DONE_AND_BEHAVIOR_PRESENT:
                return task.successor.state, task.successor.change_msg
            elif task.status == EvaluationTask.OUT_OF_RESOURCES:
                if deterministic:
                    return None, (task.error_msg +
                        "\nAn evaluator ran out of resources. With the option "
                        "'deterministic' an improving successor found later "
                        "would not count.")
                else:
                    tasks_out_of_resources.add(task)
            elif task.status == EvaluationTask.CRITICAL:
                if deterministic:
                    return None, (task.error_msg +
                        "\nA critical error occurred in an evaluator. With the "
                        "option 'deterministic' an improving successor found "
                        "later would not count.")
                else:
                    logging.warning(f"{task.error_msg}\nCritical error in "
                                    f"'{task.run_dir}'")
            elif task.status == EvaluationTask.CANCELED:
                # We only cancel jobs in deterministic mode if there is an earlier reason to return.
                assert not deterministic
            else:
                assert False, f"Unexpected task status: '{task.status}'."

    message = "No improving successor was found."
    if tasks_out_of_resources:
        run_dirs = [task.run_dir for task in tasks_out_of_resources]
        run_dirs_str = "\n".join(str(s) for s in sorted(run_dirs))
        message += (
            f" Note that the following tasks ran out of resources and thus"
            f" could not successfully be checked:\n{run_dirs_str}")
    return None, message