Configurable BlackBox

The AC module allows the automatic configuration of configurable blackbox.

These BlackBox can be objects or classes and can either be your entrypoint or globally configurable.

BlackBox Class as entrypoint

An entrypoint is a function/blackbox that the AC tool will call to perform its configuration process. Any configurable blackbox can be an entrypoint to the AC tool. Your entrypoint may also be a configurable function.

This page will talk about BlackBox as entrypoints, for more details on entrypoints, check the documentation on function entrypoints.

Consider the following configurable BlackBox:

 1from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver
 2from optilog.tuning import ac, Int, Real, Bool
 3from optilog.tuning.configurators import GGAScenario
 4
 5class ExampleSolverBB(SystemBlackBox):
 6
 7    config = {
 8        'incReduceDB': Int(0, 2147483647, default=300),
 9        'R': Real(1.001, 4.999, default=1.4),
10        'C': Bool(default=False)
11    }
12
13    def __init__(self, *args, **kwargs):
14        sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance]
15        super().__init__(arguments=sub_args, *args, **kwargs)

Tuning supports BlackBox as configurable entrypoints. In order for this BlackBox to work with tuning, we would simply pass the configurable class to the configurator:

 1if __name__ == '__main__':
 2    configurator = GGAScenario(
 3        ExampleSolverBB,
 4        constraints=ExecutionConstraints(
 5            s_wall_time=120,
 6            s_real_memory="18G",
 7            enforcer=RunSolver()
 8        ),
 9        input_data='path/to/instances/*.wcnf',
10        run_obj="quality",
11        quality_regex="^o (.+)$",
12        seed=1,
13        cost_min=0,
14        cost_max=(2 << 64) - 1,
15        cost_tolerance = 0.0
16    )
17    configurator.generate_scenario('./gga-scenario')

This code will automatically call the run method of the blackbox during the automatic configuration phase. Note that you don’t need to provide data_kwarg or seed_kwarg.

BlackBox Object as entrypoint

You may use your BlackBox class as a template for different configurable objects. If that is the case, you may be interested in using a blackbox object as a your entrypoint for configuration.

Consider the following BlackBox:

 1from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver
 2from optilog.tuning import ac, Int, Real, Bool
 3from optilog.tuning.configurators import GGAScenario
 4
 5class ExampleSolverBB(SystemBlackBox):
 6
 7    config = {
 8        'incReduceDB': Int(0, 2147483647, default=300),
 9        'R': Real(1.001, 4.999, default=1.4),
10        'C': Bool(default=False)
11    }
12
13    def __init__(self, max_exploration, *args, **kwargs):
14        sub_args = [
15            'path/to/maxsat-solver',
16            f'-max-exploration={max_exploration}',
17            SystemBlackBox.Instance
18        ]
19        super().__init__(arguments=sub_args, *args, **kwargs)

This blackbox cannot be directly configured as a class because the value of max_exploration must be provided by the user when instantiating the class. In such a case, you need to instante the class before passing it as a parameter to your configurator like so:

 1if __name__ == '__main__':
 2    solver = ExampleSolverBB(
 3        max_exploration=3
 4    )
 5    configurator = GGAScenario(
 6        solver,
 7        constraints=ExecutionConstraints(
 8            s_wall_time=120,
 9            s_real_memory="18G",
10            enforcer=RunSolver()
11        ),
12        input_data='path/to/instances/*.wcnf',
13        run_obj="quality",
14        quality_regex="^o (.+)$",
15        seed=1,
16        cost_min=0,
17        cost_max=(2 << 64) - 1,
18        cost_tolerance = 0.0
19    )
20    configurator.generate_scenario('./gga-scenario')

NOTE: Scenario generation internally uses pickle. In order for the generation of the scenario to work, your object must be pickable in order for it to be serializable and unserializable.

Extract BlackBox Command

If you have configured your blackbox entrypoint you may want to extract an execution command that can run your binary file without going through OptiLog.

OptiLog offers a way to extract the command given a parsed configuration from an executed AC tool.

In the following example we parse the output of GGA ./gga_out.o and are then enable to generate the execution command of a Binary BlackBox entrypoint by using the static get_command method.

from optilog.tuning.configurators.utils import GGAParser
from optilog.tuning.configurators import GGAScenario

parser = GGAParser("./gga_out.o")
config = parser.get_config()
command = GGAScenario.get_command("./gga-scenario", config, "<instance-path>")
print(' '.join(command))

BlackBox Class as global configurable

It may be the case that you use an algorithm which, internally, uses a blackbox. In order to configure that blackbox, pass it to your configurator through the global_cfg parameter like so:

 1from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver
 2from optilog.tuning import ac, Int, Real, Bool
 3from optilog.tuning.configurators import GGAScenario
 4
 5class ExampleSolverBB(SystemBlackBox):
 6
 7    config = {
 8        'incReduceDB': Int(0, 2147483647, default=300),
 9        'R': Real(1.001, 4.999, default=1.4),
10        'C': Bool(default=False)
11    }
12
13    def __init__(self, *args, **kwargs):
14        sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance]
15        super().__init__(arguments=sub_args, *args, **kwargs)
16
17@ac
18def entrypoint(data, seed, n: Int(0, 5) = 3):
19    ....
20
21if __name__ == '__main__':
22    configurator = GGAScenario(
23        entrypoint,
24        global_cfg=[ExampleSolverBB],
25        data_kwarg='data',
26        seed_kwarg='seed',
27        input_data='path/to/instances/*.wcnf',
28        constraints=ExecutionConstraints(
29            s_wall_time=120,
30            s_real_memory="18G",
31            enforcer=RunSolver()
32        ),
33        run_obj="quality",
34        quality_regex="^o (\d+)$",
35        seed=1,
36        cost_min=0,
37        cost_max=(2 << 64) - 1,
38    )
39    configurator.generate_scenario('./gga-scenario')

NOTE: BlackBox objects are not supported as globally configurable.

BlackBox as configurable parameter

All the blackbox described up until this point where globally configured. This means that any function that calls these blackboxes will automatically interact with a configured version of the class.

Tuning also supports the concept of a locally configured blackbox. This is analogous to local configurable functions. For more information about the differences between globally and locally configured, please refer to the page on local configurable functions.

A locally configured BlackBox can be annotated with CfgCls and CfgObj.

CfgObj is just a convenient way to annotate a configurable class, while CfgCls is a more general and powerful way of annotating your blackboxes.

Here is an example with CfgCls:

 1from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver
 2from optilog.tuning import ac, Int, Real, Bool, CfgCls
 3from optilog.tuning.configurators import GGAScenario
 4
 5class ExampleSolverBB(SystemBlackBox):
 6
 7    config = {
 8        'incReduceDB': Int(0, 2147483647, default=300),
 9        'R': Real(1.001, 4.999, default=1.4),
10        'C': Bool(default=False)
11    }
12
13    def __init__(self, max_exploration, *args, **kwargs):
14        sub_args = [
15            'path/to/maxsat-solver',
16            f'-max-exploration={max_exploration}',
17            SystemBlackBox.Instance
18        ]
19        super().__init__(arguments=sub_args, *args, **kwargs)
20@ac
21def func(instance, seed, init_solver_fn: CfgCls(ExampleSolverBB)):
22    solver = init_solver_fn(max_exploration=3)
23    ...
24
25if __name__ == '__main__':
26    configurator = GGAScenario(
27        func,
28        global_cfg=[ExampleSolverBB],
29        data_kwarg='instance',
30        seed_kwarg='seed',
31        input_data='path/to/instances/*.wcnf',
32        constraints=ExecutionConstraints(
33            s_wall_time=120,
34            s_real_memory="18G",
35            enforcer=RunSolver()
36        ),
37        run_obj="quality",
38        quality_regex="^o (\d+)$",
39        seed=1,
40        cost_min=0,
41        cost_max=(2 << 64) - 1,
42    )
43    configurator.generate_scenario('./gga-scenario')

In this example, the parameter init_solver_fn is a callback capable of instantiating a preconfigured solver. Notice that this callback can be called an undetermined number of times, which allows to instantiate multiple solvers.

However, if you want to configure a single instance of a BlackBox and this instance does not have any required parameter in its constructor, you can simplify your code using CfgObj like so:

 1from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver
 2from optilog.tuning import ac, Int, Real, Bool, CfgObj
 3from optilog.tuning.configurators import GGAScenario
 4
 5
 6class ExampleSolverBB(SystemBlackBox):
 7
 8    config = {
 9        'incReduceDB': Int(0, 2147483647, default=300),
10        'R': Real(1.001, 4.999, default=1.4),
11        'C': Bool(default=False)
12    }
13
14    def __init__(self, *args, **kwargs):
15        sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance ]
16        super().__init__(arguments=sub_args, *args, **kwargs)
17@ac
18def func(instance, seed, solver: CfgObj(ExampleSolverBB)):
19    ...
20
21if __name__ == '__main__':
22    configurator = GGAScenario(
23        func,
24        global_cfg=[ExampleSolverBB],
25        data_kwarg='instance',
26        seed_kwarg='seed',
27        input_data='./*.py',
28        constraints=ExecutionConstraints(
29            s_wall_time=120,
30            s_real_memory="18G",
31            enforcer=RunSolver()
32        ),
33        run_obj="quality",
34        quality_regex="^o (\d+)$",
35        seed=1,
36        cost_min=0,
37        cost_max=(2 << 64) - 1,
38    )
39    configurator.generate_scenario('./gga-scenario')