コード例 #1
0
ファイル: test_repeater.py プロジェクト: dpukhkaiev/BRISE2
def measure_task(configurations_sample: list, tasks_sample: list,
                 experiment_description: dict, search_space: Hyperparameter,
                 measured_tasks: int, config_type: Configuration.Type,
                 config_status: Configuration.Status):
    """
    Test function for Repeater module.
    Main steps:
    0. Take default tasks sample.
    1. Create instances of Repeater, Experiment, Default Configuration according to test requirements.
    2. Create instance of current measurement.
    3. Call Repeater function.

    :param configurations_sample: default sample of measured configurations
    :param tasks_sample: default sample of measured tasks
    :param experiment_description: Experiment Description sample in json format
    :param search_space: Search Space sample
    :param measured_tasks: specify number of measured tasks in current measurement.
    :param config_type: specify current measurement configuration type.
    :param config_status: specify current measurement configuration status.

    :return: list of configuration status and number of tasks to measure.
    """
    experiment = Experiment(experiment_description, search_space)
    Configuration.set_task_config(experiment.description["TaskConfiguration"])
    configuration = Configuration(configurations_sample[1]["Params"],
                                  config_type, experiment.unique_id)
    configuration.status = config_status
    for i in range(0, measured_tasks):
        configuration.add_task(tasks_sample[i])
    orchestrator = RepeaterOrchestration(experiment)
    if config_type == Configuration.Type.DEFAULT:
        orchestrator._type = orchestrator.get_repeater(True)
    else:
        orchestrator._type = orchestrator.get_repeater()
        default_configuration = Configuration(
            configurations_sample[0]["Params"], Configuration.Type.DEFAULT,
            experiment.unique_id)
        default_configuration.status = Configuration.Status.MEASURED
        default_configuration._task_number = configurations_sample[0]["Tasks"]
        default_configuration.results = configurations_sample[0]["Results"]
        default_configuration._standard_deviation = configurations_sample[0][
            "STD"]
        experiment.default_configuration = default_configuration
    task = json.dumps({"configuration": configuration.to_json()})

    dummy_channel = None
    dummy_method = None
    dummy_properties = None

    results_measurement = orchestrator.measure_configurations(
        dummy_channel, dummy_method, dummy_properties, task)

    return results_measurement
コード例 #2
0
    def get_energy_exp_solution_quality(self,
                                        experiment: Experiment,
                                        goal_column: str = 'EN') -> float:
        """
            The method is designed to derive the relative quality of solution obtained in Energy Experiment.

        :param experiment: energy Experiment instance. Experiment Description should contain ws_file(csv file) name.
        :param goal_column: Str either 'EN' or 'TIM', reflects Energy and Time goals respectively.
        :return: float. Quality of solution suggested by model, compared to the best one, available in the Scenario file.
        """
        file_name = experiment.description["TaskConfiguration"]["Scenario"][
            "ws_file"]
        csv_data = self.__cache["csv_data"].get(file_name)

        if csv_data is None:
            csv_data = pd.read_csv(
                './scenarios/energy_consumption/search_space_96/' +
                file_name).groupby(["FR", "TR"]).mean()
            self.__cache["csv_data"][file_name] = csv_data

        optimum_energy, _ = csv_data.min()[goal_column], csv_data.idxmin(
        )[goal_column]

        found_configuration = experiment.get_current_solution().parameters
        found_energy = csv_data.loc[tuple(found_configuration), goal_column]
        return optimum_energy / found_energy
コード例 #3
0
    def __init__(self, experiment: Experiment):

        self.logger = logging.getLogger(__name__)
        self.experiment = experiment
        self.performed_measurements = 0
        self.event_host = os.getenv("BRISE_EVENT_SERVICE_HOST")
        self.event_port = os.getenv("BRISE_EVENT_SERVICE_AMQP_PORT")
        self.repeater_parameters = self.experiment.description["Repeater"][
            "Parameters"]
        self._objectives = self.experiment.description["TaskConfiguration"][
            "Objectives"]
        self._objectives_data_types = self.experiment.description[
            "TaskConfiguration"]["ObjectivesDataTypes"]
        self._expected_values_range = self.experiment.description[
            "TaskConfiguration"]["ExpectedValuesRange"]
        self.database = MongoDB(os.getenv("BRISE_DATABASE_HOST"),
                                os.getenv("BRISE_DATABASE_PORT"),
                                os.getenv("BRISE_DATABASE_NAME"),
                                os.getenv("BRISE_DATABASE_USER"),
                                os.getenv("BRISE_DATABASE_PASS"))
        if self.experiment.description["OutliersDetection"]["isEnabled"]:
            self.outlier_detectors = get_outlier_detectors(
                experiment.get_outlier_detectors_parameters())
        else:
            self.logger.info("Outliers detection module is disabled")
        self._type = self.get_repeater(True)
        if os.environ.get('TEST_MODE') != 'UNIT_TEST':
            self.listen_thread = EventServiceConnection(self)
            self.listen_thread.start()
コード例 #4
0
ファイル: table.py プロジェクト: ITS-Zah/BRISE2
def get_relative_improvement(experiment: Experiment) -> float:
    """Division of a Solution Configuration result to the Default Configuraiton.
    :param experiment: Experiment - Instance of Experiment Class.
    Returns:
        [Float] -- Round number in percent. If Default Configuration is 0 - returns 'inf'
    """
    default_avg_result = experiment.default_configuration.get_average_result(
    )[0]
    solution_avg_result = experiment.get_current_solution().get_average_result(
    )[0]
    if default_avg_result == 0:
        return float('inf')
    else:
        return (default_avg_result -
                solution_avg_result) / default_avg_result * 100
コード例 #5
0
    def compare_solution_to_default(experiment: Experiment) -> dict:
        default_configuration_tasks = experiment.search_space.get_default_configuration(
        ).get_tasks()
        solution_configuration_tasks = experiment.get_current_solution(
        ).get_tasks()

        default_results = BRISEBenchmarkAnalyser.summarize_results_in_tasks(
            default_configuration_tasks)
        solution_results = BRISEBenchmarkAnalyser.summarize_results_in_tasks(
            solution_configuration_tasks)

        comparison = {}
        for goal in default_results.keys():
            comparison[goal] = default_results[goal] / solution_results[goal]

        return comparison
コード例 #6
0
    def evaluate(self, current_configuration: Configuration,
                 experiment: Experiment):
        """
        Return number of measurements to finish Configuration or 0 if it finished.
        In other case - compute result as average between all experiments.
        :param current_configuration: instance of Configuration class
        :param experiment: instance of 'experiment' is required for experiment-awareness.
        :return: int min_tasks_per_configuration if Configuration was not measured at all
                 or 1 if Configuration was not measured precisely or 0 if it finished
        """
        tasks_data = current_configuration.get_tasks()

        if len(tasks_data) == 0:
            return 1

        c_c_results = current_configuration.results
        c_s_results = experiment.get_current_solution().results
        c_c_results_l = []
        c_s_results_l = []
        for key in experiment.get_objectives():
            c_c_results_l.append(c_c_results[key])
            c_s_results_l.append(c_s_results[key])

        if len(tasks_data) < self.min_tasks_per_configuration:
            if self.is_experiment_aware:
                ratios = [
                    cur_config_dim / cur_solution_dim
                    for cur_config_dim, cur_solution_dim in zip(
                        c_c_results_l, c_s_results_l)
                ]
                if all([
                        ratio >= ratio_max
                        for ratio, ratio_max in zip(ratios, self.ratios_max)
                ]):
                    return 0
            return self.min_tasks_per_configuration - len(tasks_data)

        elif len(tasks_data) >= self.max_tasks_per_configuration:
            return 0
        else:
            # Calculating standard deviation
            all_dim_std = current_configuration.get_standard_deviation()

            # The number of Degrees of Freedom generally equals the number of observations (Tasks) minus
            # the number of estimated parameters.
            degrees_of_freedom = len(tasks_data) - len(c_c_results_l)

            # Calculate the critical t-student value from the t distribution
            student_coefficients = [
                t.ppf(c_l, df=degrees_of_freedom)
                for c_l in self.confidence_levels
            ]

            # Calculating confidence interval for each dimension, that contains a confidence intervals for
            # singular measurements and confidence intervals for multiple measurements.
            # First - singular measurements errors:
            conf_intervals_sm = []
            for c_l, d_s_a, d_a_c, avg in zip(self.confidence_levels,
                                              self.device_scale_accuracies,
                                              self.device_accuracy_classes,
                                              c_c_results_l):
                d = sqrt((c_l * d_s_a / 2)**2 + (d_a_c * avg / 100)**2)
                conf_intervals_sm.append(c_l * d)

            # Calculation of confidence interval for multiple measurements:
            conf_intervals_mm = []
            for student_coefficient, dim_skd in zip(student_coefficients,
                                                    all_dim_std):
                conf_intervals_mm.append(student_coefficient * dim_skd /
                                         sqrt(len(tasks_data)))

            # confidence interval, or in other words absolute error
            absolute_errors = []
            for c_i_ss, c_i_mm in zip(conf_intervals_sm, conf_intervals_mm):
                absolute_errors.append(sqrt(pow(c_i_ss, 2) + pow(c_i_mm, 2)))

            # Calculating relative error for each dimension
            relative_errors = []
            for interval, avg_res in zip(absolute_errors, c_c_results_l):
                if not avg_res:  # it is 0 or 0.0
                    # if new use-cases appear with the same behaviour.
                    if interval == 0:
                        avg_res = 1  # Anyway relative error will be 0 and avg will not be changed.
                    else:
                        return 1
                relative_errors.append(interval / avg_res * 100)

            # Thresholds for relative errors that should not be exceeded for accurate measurement.
            thresholds = []
            if self.is_experiment_aware:
                # We adapt thresholds
                objectives_minimization = experiment.get_objectives_minimization(
                )

                for i in range(len(objectives_minimization)):
                    if objectives_minimization[i]:
                        if not c_s_results_l[i]:
                            ratio = 1
                        else:
                            ratio = c_c_results_l[i] / c_s_results_l[i]
                    else:
                        if not c_c_results_l[i]:
                            ratio = 1
                        else:
                            ratio = c_s_results_l[i] / c_c_results_l[i]

                    adopted_threshold = \
                        self.base_acceptable_errors[i] \
                        + (self.max_acceptable_errors[i] - self.base_acceptable_errors[i]) \
                        / (1 + exp(- (10 / self.ratios_max[i]) * (ratio - self.ratios_max[i] / 2)))

                    thresholds.append(adopted_threshold)

            else:
                # Or we don't adapt thresholds
                for acceptable_error in self.base_acceptable_errors:
                    thresholds.append(acceptable_error)

            # Simple implementation of possible multi-dim Repeater decision making:
            # If any of resulting dimensions are not accurate - just terminate.
            for threshold, error in zip(thresholds, relative_errors):
                if error > threshold:
                    return 1
            return 0
コード例 #7
0
ファイル: test_database.py プロジェクト: dpukhkaiev/BRISE2
 def initialize_exeriment(self):
     experiment_description, search_space = load_experiment_setup(
         experiment_description_file)
     experiment = Experiment(experiment_description, search_space)
     return experiment, search_space
コード例 #8
0
    def run(self):
        """
        The entry point to the main node functionality - measuring default Configuration.
        When the default Configuration finishes its evaluation, the first set of Configurations will be
        sampled for evaluation (respectively, the queues for Configuration measurement results initialize).
        """
        self._state = self.State.RUNNING
        self.logger.info("Starting BRISE")
        self.sub.send('log', 'info', message="Starting BRISE")

        if not self.experiment_setup:
            # Check if main.py running with a specified experiment description file path
            if len(argv) > 1:
                exp_desc_file_path = argv[1]
            else:
                exp_desc_file_path = './Resources/EnergyExperiment/EnergyExperiment.json'
                log_msg = f"The Experiment Setup was not provided and the path to an experiment file was not specified." \
                          f" The default one will be executed: {exp_desc_file_path}"
                self.logger.warning(log_msg)
                self.sub.send('log', 'warning', message=log_msg)
            experiment_description, search_space = load_experiment_setup(
                exp_desc_file_path)
        else:
            experiment_description = self.experiment_setup[
                "experiment_description"]
            search_space = self.experiment_setup["search_space"]

        validate_experiment_description(experiment_description)
        os.makedirs(experiment_description["General"]["results_storage"],
                    exist_ok=True)

        # Initializing instance of Experiment - main data holder.
        self.experiment = Experiment(experiment_description, search_space)
        search_space.experiment_id = self.experiment.unique_id
        Configuration.set_task_config(
            self.experiment.description["TaskConfiguration"])

        # initialize connection to rabbitmq service
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters(
                os.getenv("BRISE_EVENT_SERVICE_HOST"),
                int(os.getenv("BRISE_EVENT_SERVICE_AMQP_PORT"))))
        self.consume_channel = self.connection.channel()

        # initialize connection to the database
        self.database = MongoDB(os.getenv("BRISE_DATABASE_HOST"),
                                int(os.getenv("BRISE_DATABASE_PORT")),
                                os.getenv("BRISE_DATABASE_NAME"),
                                os.getenv("BRISE_DATABASE_USER"),
                                os.getenv("BRISE_DATABASE_PASS"))

        # write initial settings to the database
        self.database.write_one_record(
            "Experiment_description",
            self.experiment.get_experiment_description_record())
        self.database.write_one_record(
            "Search_space",
            get_search_space_record(self.experiment.search_space,
                                    self.experiment.unique_id))
        self.experiment.send_state_to_db()

        self.sub.send(
            'experiment',
            'description',
            global_config=self.experiment.description["General"],
            experiment_description=self.experiment.description,
            searchspace_description=self.experiment.search_space.serialize(
                True))
        self.logger.debug(
            "Experiment description and global configuration sent to the API.")

        # Create and launch Stop Condition services in separate threads.
        launch_stop_condition_threads(self.experiment.unique_id)

        # Instantiate client for Worker Service, establish connection.
        self.wsc_client = WSClient(
            self.experiment.description["TaskConfiguration"],
            os.getenv("BRISE_EVENT_SERVICE_HOST"),
            int(os.getenv("BRISE_EVENT_SERVICE_AMQP_PORT")))

        # Initialize Repeater - encapsulate Configuration evaluation process to avoid results fluctuations.
        # (achieved by multiple Configuration evaluations on Workers - Tasks)
        RepeaterOrchestration(self.experiment)

        self.predictor: Predictor = Predictor(self.experiment.unique_id,
                                              self.experiment.description,
                                              self.experiment.search_space)

        self.consume_channel.basic_consume(
            queue='default_configuration_results_queue',
            auto_ack=True,
            on_message_callback=self.get_default_configurations_results)
        self.consume_channel.basic_consume(
            queue='configurations_results_queue',
            auto_ack=True,
            on_message_callback=self.get_configurations_results)
        self.consume_channel.basic_consume(queue='stop_experiment_queue',
                                           auto_ack=True,
                                           on_message_callback=self.stop)
        self.consume_channel.basic_consume(
            queue="get_new_configuration_queue",
            auto_ack=True,
            on_message_callback=self.send_new_configurations_to_measure)

        self.default_config_handler = get_default_config_handler(
            self.experiment)
        temp_msg = "Measuring default Configuration."
        self.logger.info(temp_msg)
        self.sub.send('log', 'info', message=temp_msg)
        default_parameters = self.experiment.search_space.generate_default()
        default_configuration = Configuration(default_parameters,
                                              Configuration.Type.DEFAULT,
                                              self.experiment.unique_id)
        default_configuration.experiment_id = self.experiment.unique_id
        dictionary_dump = {"configuration": default_configuration.to_json()}
        body = json.dumps(dictionary_dump)

        self.consume_channel.basic_publish(
            exchange='',
            routing_key='measure_new_configuration_queue',
            body=body)
        # listen all queues with responses until the _is_interrupted flag is False
        try:
            while not self._is_interrupted:
                self.consume_channel.connection.process_data_events(
                    time_limit=1)  # 1 second
        finally:
            if self.connection.is_open:
                self.connection.close()
コード例 #9
0
class MainThread(threading.Thread):
    """
    This class runs Main functionality in a separate thread,
    connected to the `default_configuration_results_queue` and `configurations results queue` as a consumer.
    """
    class State(int, Enum):
        RUNNING = 0
        SHUTTING_DOWN = 1
        IDLE = 2

    def __init__(self, experiment_setup: [Experiment, Hyperparameter] = None):
        """
        The function for initializing main thread
        :param experiment_setup: fully initialized experiment, r.g from a POST request
        """
        super(MainThread, self).__init__()
        self._is_interrupted = False
        self.conf_lock = threading.Lock()
        self._state = self.State.IDLE
        self.experiment_setup = experiment_setup

        self.sub = API()  # front-end subscribers
        if __name__ == "__main__":
            self.logger = BRISELogConfigurator().get_logger(__name__)
        else:
            self.logger = logging.getLogger(__name__)

        self.experiment: Experiment = None
        self.connection: pika.BlockingConnection = None
        self.consume_channel = None
        self.predictor: Predictor = None
        self.wsc_client: WSClient = None
        self.repeater: RepeaterOrchestration = None
        self.database: MongoDB = None

    def run(self):
        """
        The entry point to the main node functionality - measuring default Configuration.
        When the default Configuration finishes its evaluation, the first set of Configurations will be
        sampled for evaluation (respectively, the queues for Configuration measurement results initialize).
        """
        self._state = self.State.RUNNING
        self.logger.info("Starting BRISE")
        self.sub.send('log', 'info', message="Starting BRISE")

        if not self.experiment_setup:
            # Check if main.py running with a specified experiment description file path
            if len(argv) > 1:
                exp_desc_file_path = argv[1]
            else:
                exp_desc_file_path = './Resources/EnergyExperiment/EnergyExperiment.json'
                log_msg = f"The Experiment Setup was not provided and the path to an experiment file was not specified." \
                          f" The default one will be executed: {exp_desc_file_path}"
                self.logger.warning(log_msg)
                self.sub.send('log', 'warning', message=log_msg)
            experiment_description, search_space = load_experiment_setup(
                exp_desc_file_path)
        else:
            experiment_description = self.experiment_setup[
                "experiment_description"]
            search_space = self.experiment_setup["search_space"]

        validate_experiment_description(experiment_description)
        os.makedirs(experiment_description["General"]["results_storage"],
                    exist_ok=True)

        # Initializing instance of Experiment - main data holder.
        self.experiment = Experiment(experiment_description, search_space)
        search_space.experiment_id = self.experiment.unique_id
        Configuration.set_task_config(
            self.experiment.description["TaskConfiguration"])

        # initialize connection to rabbitmq service
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters(
                os.getenv("BRISE_EVENT_SERVICE_HOST"),
                int(os.getenv("BRISE_EVENT_SERVICE_AMQP_PORT"))))
        self.consume_channel = self.connection.channel()

        # initialize connection to the database
        self.database = MongoDB(os.getenv("BRISE_DATABASE_HOST"),
                                int(os.getenv("BRISE_DATABASE_PORT")),
                                os.getenv("BRISE_DATABASE_NAME"),
                                os.getenv("BRISE_DATABASE_USER"),
                                os.getenv("BRISE_DATABASE_PASS"))

        # write initial settings to the database
        self.database.write_one_record(
            "Experiment_description",
            self.experiment.get_experiment_description_record())
        self.database.write_one_record(
            "Search_space",
            get_search_space_record(self.experiment.search_space,
                                    self.experiment.unique_id))
        self.experiment.send_state_to_db()

        self.sub.send(
            'experiment',
            'description',
            global_config=self.experiment.description["General"],
            experiment_description=self.experiment.description,
            searchspace_description=self.experiment.search_space.serialize(
                True))
        self.logger.debug(
            "Experiment description and global configuration sent to the API.")

        # Create and launch Stop Condition services in separate threads.
        launch_stop_condition_threads(self.experiment.unique_id)

        # Instantiate client for Worker Service, establish connection.
        self.wsc_client = WSClient(
            self.experiment.description["TaskConfiguration"],
            os.getenv("BRISE_EVENT_SERVICE_HOST"),
            int(os.getenv("BRISE_EVENT_SERVICE_AMQP_PORT")))

        # Initialize Repeater - encapsulate Configuration evaluation process to avoid results fluctuations.
        # (achieved by multiple Configuration evaluations on Workers - Tasks)
        RepeaterOrchestration(self.experiment)

        self.predictor: Predictor = Predictor(self.experiment.unique_id,
                                              self.experiment.description,
                                              self.experiment.search_space)

        self.consume_channel.basic_consume(
            queue='default_configuration_results_queue',
            auto_ack=True,
            on_message_callback=self.get_default_configurations_results)
        self.consume_channel.basic_consume(
            queue='configurations_results_queue',
            auto_ack=True,
            on_message_callback=self.get_configurations_results)
        self.consume_channel.basic_consume(queue='stop_experiment_queue',
                                           auto_ack=True,
                                           on_message_callback=self.stop)
        self.consume_channel.basic_consume(
            queue="get_new_configuration_queue",
            auto_ack=True,
            on_message_callback=self.send_new_configurations_to_measure)

        self.default_config_handler = get_default_config_handler(
            self.experiment)
        temp_msg = "Measuring default Configuration."
        self.logger.info(temp_msg)
        self.sub.send('log', 'info', message=temp_msg)
        default_parameters = self.experiment.search_space.generate_default()
        default_configuration = Configuration(default_parameters,
                                              Configuration.Type.DEFAULT,
                                              self.experiment.unique_id)
        default_configuration.experiment_id = self.experiment.unique_id
        dictionary_dump = {"configuration": default_configuration.to_json()}
        body = json.dumps(dictionary_dump)

        self.consume_channel.basic_publish(
            exchange='',
            routing_key='measure_new_configuration_queue',
            body=body)
        # listen all queues with responses until the _is_interrupted flag is False
        try:
            while not self._is_interrupted:
                self.consume_channel.connection.process_data_events(
                    time_limit=1)  # 1 second
        finally:
            if self.connection.is_open:
                self.connection.close()

    def get_default_configurations_results(self, ch, method, properties, body):
        """
        Callback function for the result of default configuration
        :param ch: pika.Channel
        :param method:  pika.spec.Basic.GetOk
        :param properties: pika.spec.BasicProperties
        :param body: result of measuring default configuration in bytes format
        """
        default_configuration = Configuration.from_json(body.decode())
        if default_configuration.status == Configuration.Status.BAD:
            new_default_values = self.default_config_handler.get_new_default_config(
            )
            if new_default_values:
                config = Configuration(new_default_values,
                                       Configuration.Type.FROM_SELECTOR,
                                       self.experiment.unique_id)
                temp_msg = "New default configuration sampled."
                self.logger.info(temp_msg)
                self.sub.send('log', 'info', message=temp_msg)
                self.consume_channel.basic_publish(
                    exchange='',
                    routing_key='measure_new_configuration_queue',
                    body=json.dumps({"configuration": config.to_json()}))
            else:
                self.logger.error(
                    "The specified default configuration is broken.")
                self.stop()
                self.sub.send(
                    'log',
                    'info',
                    message="The specified default configuration is broken.")
                return
        if self.experiment.is_configuration_evaluated(default_configuration):
            self.experiment.default_configuration = default_configuration
            self.database.update_record(
                "Search_space", {"Exp_unique_ID": self.experiment.unique_id}, {
                    "Default_configuration":
                    default_configuration.get_configuration_record()
                })
            self.database.update_record(
                "Search_space", {"Exp_unique_ID": self.experiment.unique_id}, {
                    "SearchspaceObject": pickle.dumps(
                        self.experiment.search_space)
                })

            temp_msg = f"Evaluated Default Configuration: {default_configuration}"
            self.logger.info(temp_msg)
            self.sub.send('log', 'info', message=temp_msg)

            # starting main work: building model and choosing configuration for measuring
            self.consume_channel.basic_publish(
                exchange='', routing_key='get_worker_capacity_queue', body='')

    def get_configurations_results(self, ch, method, properties, body):
        """
        Callback function for the result of all Configurations except Default
        :param ch: pika.Channel
        :param method:  pika.spec.Basic.GetOk
        :param properties: pika.spec.BasicProperties
        :param body: result of measuring any configuration except default in bytes format
        """
        with self.conf_lock:  # To be sure, that no Configuration will be added after satisfying all Stop Conditions.
            configuration = Configuration.from_json(body.decode())
            if not self._is_interrupted and self.experiment.is_configuration_evaluated(
                    configuration):
                self.experiment.try_add_configuration(configuration)
                temp_msg = "-- New Configuration was evaluated. Building Target System model."
                self.logger.info(temp_msg)
                self.sub.send('log', 'info', message=temp_msg)
                self.consume_channel.basic_publish(
                    exchange='',
                    routing_key='get_worker_capacity_queue',
                    body='')

    def send_new_configurations_to_measure(self, ch, method, properties, body):
        """
        This callback function will be triggered on arrival of ONE measured Configuration.
        When there is new measured Configuration, following steps should be done:

            -   update and validate models;

            -   pick either by model, or by selection algorithm new Configuration(s) for evaluation;
                Note: The amount of new Configurations are:
                - 0 if number of available Worker nodes decreased;
                - 1 if number of available Workers did not change;
                - N + 1 if number of available Worker increased by N;

            -   send new Configuration to Repeater for evaluation.
        """

        needed_configs = json.loads(body.decode()).get("worker_capacity", 1)
        for _ in range(needed_configs):
            config = self.predictor.predict(
                self.experiment.measured_configurations)
            if config not in self.experiment.evaluated_configurations:
                temp_msg = f"Model predicted {config}."
            else:
                while config in self.experiment.evaluated_configurations and not self._is_interrupted:
                    if len(self.experiment.evaluated_configurations
                           ) >= self.experiment.search_space.get_size():
                        msg = "Entire Search Space was evaluated. Shutting down."
                        self.logger.warning(msg)
                        self.consume_channel.basic_publish(
                            exchange='',
                            routing_key='stop_experiment_queue',
                            body=msg)
                        break

                    new_parameter_values = OrderedDict()
                    while not self.experiment.search_space.validate(
                            new_parameter_values, is_recursive=True):
                        self.experiment.search_space.generate(
                            new_parameter_values)
                    config = Configuration(new_parameter_values,
                                           Configuration.Type.FROM_SELECTOR,
                                           self.experiment.unique_id)
                temp_msg = f"Fully randomly sampled {config}."

            self.logger.info(temp_msg)
            self.sub.send('log', 'info', message=temp_msg)
            self.consume_channel.basic_publish(
                exchange='',
                routing_key='measure_new_configuration_queue',
                body=json.dumps({"configuration": config.to_json()}))

    def stop(self, ch=None, method=None, properties=None, body=None):
        """
        The function for stop main thread externally (e.g. from front-end)
        """
        with self.conf_lock:
            self.consume_channel.basic_publish(
                exchange='brise_termination_sender', routing_key='', body='')
            self.sub.send('log',
                          'info',
                          message=f"Terminating experiment. Reason: {body}")
            self._state = self.State.SHUTTING_DOWN
            self._is_interrupted = True
            optimal_configuration = self.experiment.get_final_report_and_result(
            )
            self._state = self.State.IDLE
            return optimal_configuration

    def get_state(self):
        return self._state