.. _bb-as-config-params: 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**. .. _bb-entrypoint: 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 :ref:`configurable function `. This page will talk about BlackBox as entrypoints, for more details on entrypoints, check the documentation on :ref:`function entrypoints `. Consider the following configurable BlackBox: .. code:: python :number-lines: from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver from optilog.tuning import ac, Int, Real, Bool from optilog.tuning.configurators import GGAScenario class ExampleSolverBB(SystemBlackBox): config = { 'incReduceDB': Int(0, 2147483647, default=300), 'R': Real(1.001, 4.999, default=1.4), 'C': Bool(default=False) } def __init__(self, *args, **kwargs): sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance] 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: .. code:: python :number-lines: if __name__ == '__main__': configurator = GGAScenario( ExampleSolverBB, constraints=ExecutionConstraints( s_wall_time=120, s_real_memory="18G", enforcer=RunSolver() ), input_data='path/to/instances/*.wcnf', run_obj="quality", quality_regex="^o (.+)$", seed=1, cost_min=0, cost_max=(2 << 64) - 1, cost_tolerance = 0.0 ) 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: .. code:: python :number-lines: from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver from optilog.tuning import ac, Int, Real, Bool from optilog.tuning.configurators import GGAScenario class ExampleSolverBB(SystemBlackBox): config = { 'incReduceDB': Int(0, 2147483647, default=300), 'R': Real(1.001, 4.999, default=1.4), 'C': Bool(default=False) } def __init__(self, max_exploration, *args, **kwargs): sub_args = [ 'path/to/maxsat-solver', f'-max-exploration={max_exploration}', SystemBlackBox.Instance ] 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: .. code:: python :number-lines: if __name__ == '__main__': solver = ExampleSolverBB( max_exploration=3 ) configurator = GGAScenario( solver, constraints=ExecutionConstraints( s_wall_time=120, s_real_memory="18G", enforcer=RunSolver() ), input_data='path/to/instances/*.wcnf', run_obj="quality", quality_regex="^o (.+)$", seed=1, cost_min=0, cost_max=(2 << 64) - 1, cost_tolerance = 0.0 ) 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. .. _bb_command: 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. .. code:: python 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, "") print(' '.join(command)) .. _bb_global_config: 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: .. code:: python :number-lines: from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver from optilog.tuning import ac, Int, Real, Bool from optilog.tuning.configurators import GGAScenario class ExampleSolverBB(SystemBlackBox): config = { 'incReduceDB': Int(0, 2147483647, default=300), 'R': Real(1.001, 4.999, default=1.4), 'C': Bool(default=False) } def __init__(self, *args, **kwargs): sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance] super().__init__(arguments=sub_args, *args, **kwargs) @ac def entrypoint(data, seed, n: Int(0, 5) = 3): .... if __name__ == '__main__': configurator = GGAScenario( entrypoint, global_cfg=[ExampleSolverBB], data_kwarg='data', seed_kwarg='seed', input_data='path/to/instances/*.wcnf', constraints=ExecutionConstraints( s_wall_time=120, s_real_memory="18G", enforcer=RunSolver() ), run_obj="quality", quality_regex="^o (\d+)$", seed=1, cost_min=0, cost_max=(2 << 64) - 1, ) configurator.generate_scenario('./gga-scenario') **NOTE**: BlackBox objects are not supported as globally configurable. .. _bb_nested: 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 :ref:`local configurable functions `. For more information about the differences between **globally** and **locally** configured, please refer to the page on :ref:`local configurable functions `. A **locally** configured BlackBox can be annotated with :class:`CfgCls ` and :class:`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: .. code:: python :number-lines: from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver from optilog.tuning import ac, Int, Real, Bool, CfgCls from optilog.tuning.configurators import GGAScenario class ExampleSolverBB(SystemBlackBox): config = { 'incReduceDB': Int(0, 2147483647, default=300), 'R': Real(1.001, 4.999, default=1.4), 'C': Bool(default=False) } def __init__(self, max_exploration, *args, **kwargs): sub_args = [ 'path/to/maxsat-solver', f'-max-exploration={max_exploration}', SystemBlackBox.Instance ] super().__init__(arguments=sub_args, *args, **kwargs) @ac def func(instance, seed, init_solver_fn: CfgCls(ExampleSolverBB)): solver = init_solver_fn(max_exploration=3) ... if __name__ == '__main__': configurator = GGAScenario( func, global_cfg=[ExampleSolverBB], data_kwarg='instance', seed_kwarg='seed', input_data='path/to/instances/*.wcnf', constraints=ExecutionConstraints( s_wall_time=120, s_real_memory="18G", enforcer=RunSolver() ), run_obj="quality", quality_regex="^o (\d+)$", seed=1, cost_min=0, cost_max=(2 << 64) - 1, ) 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: .. code:: python :number-lines: from optilog.blackbox import SystemBlackBox, ExecutionConstraints, RunSolver from optilog.tuning import ac, Int, Real, Bool, CfgObj from optilog.tuning.configurators import GGAScenario class ExampleSolverBB(SystemBlackBox): config = { 'incReduceDB': Int(0, 2147483647, default=300), 'R': Real(1.001, 4.999, default=1.4), 'C': Bool(default=False) } def __init__(self, *args, **kwargs): sub_args = ['path/to/maxsat-solver', SystemBlackBox.Instance ] super().__init__(arguments=sub_args, *args, **kwargs) @ac def func(instance, seed, solver: CfgObj(ExampleSolverBB)): ... if __name__ == '__main__': configurator = GGAScenario( func, global_cfg=[ExampleSolverBB], data_kwarg='instance', seed_kwarg='seed', input_data='./*.py', constraints=ExecutionConstraints( s_wall_time=120, s_real_memory="18G", enforcer=RunSolver() ), run_obj="quality", quality_regex="^o (\d+)$", seed=1, cost_min=0, cost_max=(2 << 64) - 1, ) configurator.generate_scenario('./gga-scenario')