Examples

Example Target Algorithm Wrapper

With this example, the target algorithm to be configured needs to be callable via command line and accept parameters via command line. It also has to provide runtime output to the terminal in order to be used for the gray-box extension.

from subprocess import Popen, PIPE
import subprocess
from typing import Any
import time
import sys
import os
from rtac.wrapper.abstract_wrapper import AbstractWrapper
from rtac.ac_functionalities.rtac_data import Configuration, InterimMeaning
from rtac.ac_functionalities.ta_runner import non_block_read

sys.path.append(os.getcwd())


class TSP_RT(AbstractWrapper):
    """TSP wrapper for runtime scenario."""

    def translate_config(self, config: Configuration) -> list[str]:

        config_list = []
        config.conf['-a'] = 0.9  # runtime scenario: fixed annealing factor
        for name, param in config.conf.items():
            config_list.append(name)
            config_list.append(str(param))

        return config_list

    def start(self, config: Any, timeout: int,
              instance: str) -> tuple[subprocess.Popen, int]:

        # Absolute path to the current file
        file_path = os.path.abspath(__file__)

        # Directory containing the file
        file_dir = os.path.dirname(file_path)
        file_dir = file_dir.split('wrapper')[0]

        proc = Popen(['python3',
                      f'{file_dir}data/solvers/python-tsp.py',
                      *config, '-t', str(timeout), '-i',
                      f'{file_dir}{instance}'],
                     stdout=PIPE)

        self.timeout = timeout

        proc_cpu_time = time.process_time_ns()

        return proc, proc_cpu_time

    def check_if_solved(self, ta_output: bytes, nnr: non_block_read,
                        proc: subprocess.Popen) -> tuple[
                            int | float, float, int] | None:

        if ta_output != b'':
            b = str(ta_output.strip())
            if 'Warning' in b:  # Appears in b, if TA reaches time limit
                time = self.timeout
                res = sys.maxsize
                event = 0

                return res, time, event

            if 'Time:' in b:
                time = float(b.split(' ')[1][:-1])
                res_not_given = True
                while res_not_given:
                    line = nnr(proc.stdout)
                    b = str(line.strip())
                    if 'Distance:' in b:
                        res = float(b.split(' ')[1][:-1])
                        res_not_given = False

                event = 1
                proc.stdout.close()

            else:

                return None

            return res, time, event
        else:
            return None


class TSP_Q(TSP_RT):
    """TSP wrapper for solution quality scenario."""

    def translate_config(self, config: Configuration) -> list[str]:

        config_list = []
        for name, param in config.conf.items():
            config_list.append(name)
            config_list.append(str(param))

        return config_list


class TSP_RTpp(TSP_RT):
    """TSP wrapper for runtime scenario with runtime output."""

    def interim_info(self) -> list[InterimMeaning]:

        self.interim_meaning = [InterimMeaning.decrease]

        return self.interim_meaning

    def check_output(self, ta_output: bytes) -> list[float] | None:

        if ta_output != b'':
            b = str(ta_output.strip())
            # Check for progress
            if 'Temperature' in b:
                b = b.split(' ')
                # Assumption: the lower the temperature, the closer the TA is
                # to finding the solution. Solution Quality is not regarded in
                # this example, we optimize for runtime.
                temp = float(b[1][:-1])
                interim = [temp]

                return interim
            else:
                return None
        else:
            return None


class TSP_Qpp(TSP_Q):
    """TSP wrapper for solution quality scenario with runtime output."""

    def interim_info(self) -> list[InterimMeaning]:

        self.interim_meaning = [InterimMeaning.decrease,
                                InterimMeaning.increase,
                                InterimMeaning.decrease,
                                InterimMeaning.increase]

        return self.interim_meaning

    def check_output(self, ta_output) -> list[float] | None:

        if ta_output != b'':
            b = str(ta_output.strip())
            # Check for progress
            if 'Temperature' in b:
                b = b.split(' ')

                temp = float(b[1][:-1])
                k = float(b[6].split('/')[0])
                k_acc = float(b[8].split('/')[0])
                k_noimp = float(b[10][:-1])
                interim = [temp, k, k_acc, k_noimp]

                return interim
            else:
                return None
        else:
            return None

Scenario File Examples

The following scenario file examples assume a runtime minimization scenario. To minimize objective value of the target algorithm, add –objective_min. Further, the following scenario files assume the availability of 8 cores (or 4 cores with 8 threads in total, which is not optimal but still works well). If your machine has more available cores and you can spare them you should increase –number_cores. Additionally, the experimental setting is assumed here. This means that at the start of the RAC process, logs of data for tournament 0 are loaded to allow for comparability between runs. If you are not aiming at this, omit the flag –experimental.

ReACTR

To run ReACTR, you can use a scenario file like this:

--verbosity 2
--number_cores 8
--timeout 120
--contenders 30
--keeptop 2
--pws
--experimental
--chance 25
--mutate 10
--kill 5
--ac 1
--paramlimit 100000
--log_folder logs
--wrapper wrapper.tsp
--wrapper_name TSP
--feature_gen absoulte_path_to/feature_gen/tsp_feats.py
--param_file absoulte_path_to/data/tsp_params.json

ReACTR++

To run ReACTR++, you can use a scenario file like this:

--verbosity 2
--number_cores 8
--timeout 120
--contenders 30
--keeptop 2
--pws
--experimental
--chance 25
--mutate 10
--kill 5
--ac 2
--paramlimit 100000
--log_folder logs
--wrapper wrapper.tsp
--wrapper_name TSPpp
--feature_gen absoulte_path_to/feature_gen/tsp_feats.py
--param_file absoulte_path_to/data/tsp_params.json

CPPL

To run CPPL, you can use a scenario file like this:

--verbosity 2
--number_cores 8
--timeout 120
--runtimePAR 1
--contenders 30
--keeptop 4
--pws
--experimental
--chance 25
--mutate 10
--online_instance_train
--nc_pca_f 3
--nc_pca_p 5
--jfm polynomial
--omega 1.0
--gamma 1
--alpha 0.2
--epsilon 0.9
--kappa 1.0
--ac 3
--paramlimit 100000
--epsilon_greedy
--gen_mult 2
--log_folder logs
--wrapper wrapper.tsp
--wrapper_name TSPpp
--feature_gen feature_gen.tsp_feats
--feature_gen_name TSPFeats
--feature_path absoulte_path_to/feature_gen/cad_features/Features_tsp.csv
--param_file absoulte_path_to/data/tsp_params.json
--instance_pre_train absoulte_path_to/feature_gen/tsp_features/pre_train_features.txt

You do not need to provide –feature_path if you are computing problem instance features online. Pre-train features need to be in a text file as Python lists in an individual line per instance.

Gray-Box

To run CPPL in gray-box mode, you can use a scenario file like this:

--verbosity 2
--number_cores 8
--timeout 120
--runtimePAR 1
--contenders 30
--keeptop 4
--pws
--experimental
--chance 25
--mutate 10
--online_instance_train
--nc_pca_f 3
--nc_pca_p 5
--jfm polynomial
--omega 1.0
--gamma 1
--alpha 0.2
--epsilon 0.9
--kappa 1.0
--ac 3
--paramlimit 100000
--epsilon_greedy
--gen_mult 2
--log_folder logs
--gray_box
--gb_read_time 0.1
--nr_gb_feats 2
--wrapper wrapper.tsp
--wrapper_name TSPpp
--feature_gen feature_gen.tsp_feats
--feature_gen_name TSPFeats
--feature_path absoulte_path_to/feature_gen/cad_features/Features_tsp.csv
--param_file absoulte_path_to/data/tsp_params.json
--instance_pre_train absoulte_path_to/feature_gen/tsp_features/pre_train_features.txt

You do not need to provide –feature_path if you are computing problem instance features online. You can run ReACTR and ReACTR++ in gray-box mode by adding –gray_box and –nr_gb_feats and using a wrapper with runtime output mechanism. Pre-train features need to be in a text file as Python lists in an individual line per instance.

Parameter Files

Currently, two ways of declaring parameter space definition are possible in RTAC, PCS and a custom json based on a json schema.

PCS

You can find documentation for PCS at PCS Documentation. Here is an example:

ps categorical {ps1, ps2, ps3, ps4, ps5, ps6, two_opt} [two_opt]
a real [0.0001, 0.9999] [0.9]
mni integer [1, 100] [3]
miim integer [1, 100] [10]

JSON

RTAC has an own json schema for parameter space definition.

Each parameter is defined as a dictionary key in a JSON object.

Required Field

  • ``paramtype``: must be one of "categorical", "discrete", "continuous", or "binary".

Fields by Type

  • Continuous

    • Requires: minval, maxval (both numbers), default (number)

  • Discrete

    • Requires: minval, maxval (both integers), default (integer)

  • Binary

    • Requires: default (integer or string)

  • Categorical

    • If valtype is "int":

      • Requires: minval, maxval (integers), valtype

    • If valtype is "str":

      • Requires: values (array of strings), valtype

      • Optional: default (string)

Optional Common Fields

  • default: default value (number, integer, or string depending on type)

  • flag: if true, this is a binary flag, not a parameter

  • distribution: either "uniform" or "log" (defaults to uniform)

Log Distribution Options (if used)

Requires distribution = "log", and optionally allows:

  • splitbydefault: split log range at default value

  • logonpos / logonneg: enable log sampling on positive/negative side (if minval < 0)

  • probabpos, probabneg: probability of sampling positive/negative side (between 0 and 1, exclusive)

  • includezero: include zero in log sampling

  • probabilityzero: probability of choosing zero (between 0 and 1, exclusive)

Example

{‘param_6mdb’: {‘paramtype’: ‘discrete’,

‘minval’: 0, ‘maxval’: 10, ‘default’: 0},

‘param_98dt’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_f3ed’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘int’, ‘minval’: 1, ‘maxval’: 3, ‘default’: 3},

‘param_bcnn’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_3zry’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘false’},

‘param_7oqx’: {‘paramtype’: ‘discrete’,

‘minval’: 0, ‘maxval’: 2000000000, ‘default’: 100000, ‘distribution’: ‘log’, ‘includezero’: True, ‘probabilityzero’: 0.05, ‘logonpos’: True, ‘probabpos’: 0.9},

‘param_gids’: {‘paramtype’: ‘discrete’,

‘minval’: 2, ‘maxval’: 2000000000, ‘default’: 4, ‘distribution’: ‘log’, ‘logonpos’: True, ‘probabpos’: 0.9},

‘param_1dly’: {‘paramtype’: ‘discrete’,

‘minval’: 0, ‘maxval’: 2000000000, ‘default’: 100, ‘distribution’: ‘log’, ‘includezero’: True, ‘probabilityzero’: 0.05, ‘logonpos’: True, ‘probabpos’: 0.9},

‘param_t43b’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_mb9s’: {‘paramtype’: ‘discrete’,

‘minval’: 1, ‘maxval’: 3, ‘default’: 1},

‘param_6o9q’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘false’},

‘param_ovbg’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_luvg’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_p9dc’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘false’},

‘param_g0aj’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_imgg’: {‘paramtype’: ‘discrete’,

‘minval’: 0, ‘maxval’: 2, ‘default’: 1},

‘param_hpvo’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘false’},

‘param_bc51’: {‘paramtype’: ‘discrete’,

‘minval’: 0, ‘maxval’: 2000000000, ‘default’: 100, ‘distribution’: ‘log’, ‘splitbydefault’: True, ‘includezero’: True, ‘probabilityzero’: 0.05, ‘logonpos’: True, ‘probabpos’: 0.9},

‘param_6040’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_m6pf’: {‘paramtype’: ‘categorical’,

‘valtype’: ‘str’, ‘values’: [‘true’, ‘false’], ‘default’: ‘true’},

‘param_wu3s’: {‘paramtype’: ‘discrete’,

‘minval’: 1, ‘maxval’: 2000000000, ‘default’: 2000, ‘distribution’: ‘log’, ‘splitbydefault’: True, ‘logonpos’: True, ‘probabpos’: 0.8}}

Example RTAC call

You can then use a python script in the following manner to solve incoming problem instances:

from rtac.utils.read_io import read_args
from rtac.rtac import rtac_factory
import sys


def main(scenario, instance_file):
    '''Run RAC process on, potentially infinite, problem instance sequence.'''

    instances = []
    with open(f'{instance_file}', 'r') as f:
        for line in f:
            instances.append(line.strip())

    rtac = rtac_factory(scenario)

    if scenario.gray_box:
        for i, instance in enumerate(instances):
            rtac.solve_instance(instance, next_instance=None)
            # If next problem instance arrives after rtac is started, it can be
            # passed while the configurator runs on current problem instance
            if i + 1 <= len(instances):
                rtac.provide_early_instance(instances[i + 1])
            # GB RAC needs to be wrapped up after running an iteration
            rtac.wrap_up_gb()
    else:
        for instance in instances:
            rtac.solve_instance(instance)


if __name__ == '__main__':
    scenario = read_args('./scenario.txt', sys.argv)
    instance_file = './instance_sequence.txt'

    main(scenario, instance_file)

Naturally, you can adjust the script to wait and receive problem instances to be passed to rtac.solve_instance and / or rtac.provide_early_instance.