"""Function Approximation Benchmark."""
from __future__ import annotations
from pathlib import Path
import ConfigSpace as CS # noqa: N817
import ConfigSpace.hyperparameters as CSH
import numpy as np
import pandas as pd
from dacbench.abstract_benchmark import AbstractBenchmark, objdict
from dacbench.envs import FunctionApproximationEnv, FunctionApproximationInstance
from dacbench.envs.env_utils.toy_functions import get_toy_function
# This configures two action dimensions (functions):
# - The first function should be approximated in a continuous manner
# - The second function should be approximated in a discrete manner
# for which 10 actions are available
DISCRETE = (False, 10)
DEFAULT_CFG_SPACE = CS.ConfigurationSpace()
DIM1 = CSH.UniformFloatHyperparameter(name="value_dim_1", lower=0, upper=1)
DIM2 = CSH.UniformIntegerHyperparameter(name="value_dim_2", lower=0, upper=10)
DEFAULT_CFG_SPACE.add(DIM1)
DEFAULT_CFG_SPACE.add(DIM2)
INFO = {
"identifier": "FunctionApproximation",
"name": "Function Approximation",
"reward": "Multiplied Differences between Function and Action in each Dimension",
"state_description": [
"Remaining Budget",
"Function Identifier (dimension 1)",
"Function Parameter 1 (dimension 1)",
"Function Parameter 2 (dimension 1)",
"Function Identifier (dimension 2)",
"Function Parameter 1 (dimension 2)",
"Function Parameter 2 (dimension 2)",
"Action 1",
"Action 2",
],
}
FUNCTION_APPROXIMATION_DEFAULTS = objdict(
{
"config_space": DEFAULT_CFG_SPACE,
"observation_space_class": "Box",
"observation_space_type": np.float32,
"observation_space_args": [
np.array([-np.inf for _ in range(1 + len(DEFAULT_CFG_SPACE) * 4)]),
np.array([np.inf for _ in range(1 + len(DEFAULT_CFG_SPACE) * 4)]),
],
"discrete": DISCRETE,
"reward_range": (0, 1),
"cutoff": 10,
"seed": 0,
"multi_agent": False,
"omit_instance_type": False,
"instance_set_path": "sigmoid_2D3M_train.csv",
"test_set_path": "sigmoid_2D3M_test.csv",
"benchmark_info": INFO,
}
)
[docs]
class FunctionApproximationBenchmark(AbstractBenchmark):
"""Benchmark with default configuration &
relevant functions for Function Approximation.
"""
def __init__(self, config_path=None, config=None):
"""Initialize Function Approximation Benchmark.
Parameters
-------
config_path : str
Path to config file (optional)
"""
super().__init__(config_path, config)
if not self.config:
self.config = objdict(FUNCTION_APPROXIMATION_DEFAULTS.copy())
for key in FUNCTION_APPROXIMATION_DEFAULTS:
if key not in self.config:
self.config[key] = FUNCTION_APPROXIMATION_DEFAULTS[key]
[docs]
def get_environment(self):
"""Return Function Approximation env with current configuration.
Returns:
-------
FunctionApproximationEnv
Function Approximation environment
"""
if "instance_set" not in self.config:
self.read_instance_set()
# Read test set if path is specified
if "test_set" not in self.config and "test_set_path" in self.config:
self.read_instance_set(test=True)
env = FunctionApproximationEnv(self.config)
for func in self.wrap_funcs:
env = func(env)
return env
[docs]
def read_instance_set(self, test=False):
"""Read instance set from file."""
if test:
path = Path(self.config.test_set_path)
relative_path = Path(__file__).resolve().parent / self.config.test_set_path
dacbench_path = (
Path(__file__).resolve().parent
/ "../instance_sets/function_approximation"
/ self.config.test_set_path
)
keyword = "test_set"
else:
path = Path(self.config.instance_set_path)
relative_path = (
Path(__file__).resolve().parent / self.config.instance_set_path
)
dacbench_path = (
Path(__file__).resolve().parent
/ "../instance_sets/function_approximation"
/ self.config.instance_set_path
)
keyword = "instance_set"
if path.is_file():
path = path # noqa: PLW0127
elif relative_path.is_file():
path = relative_path
elif dacbench_path.is_file():
path = dacbench_path
else:
raise FileNotFoundError(
f"Test set not found at {self.config.test_set_path}"
)
self.config[keyword] = {}
instances = pd.read_csv(path)
num_functions = (len(instances.columns) - 1) // 3
for i, row in instances.iterrows():
functions = []
for j in range(num_functions):
functions.append(
get_toy_function(
identifier=row[f"function_{j}_identifier"],
a=row[f"function_{j}_a"],
b=row[f"function_{j}_b"],
)
)
importances = [1 / num_functions for _ in range(num_functions)]
if "importance_dim_0" in row:
importances = [row[f"importance_dim_{j}"] for j in range(num_functions)]
self.config[keyword][i] = FunctionApproximationInstance(
functions=functions,
dimension_importances=importances,
discrete=self.config.discrete,
omit_instance_type=self.config.omit_instance_type,
)
[docs]
def get_benchmark(self, dimension=None, seed=0):
"""Get Sigmoid Benchmark from DAC paper.
Parameters
-------
dimension : int
Sigmoid dimension, was 1, 2, 3 or 5 in the paper
seed : int
Environment seed
Returns:
-------
env : SigmoidEnv
Sigmoid environment
"""
self.config = objdict(FUNCTION_APPROXIMATION_DEFAULTS.copy())
self.config.omit_instance_type = True
if dimension == 1:
self.config.instance_set_path = "sigmoid_1D3M_train.csv"
self.config.test_set_path = "sigmoid_1D3M_test.csv"
self.config.discrete = [3]
cfg_space = CS.ConfigurationSpace()
dim1 = CSH.UniformIntegerHyperparameter(
name="value_dim_1", lower=0, upper=3
)
cfg_space.add(dim1)
self.config.config_space = cfg_space
self.config.benchmark_info["state_description"] = [
"Remaining Budget",
"Shift (dimension 1)",
"Slope (dimension 1)",
"Action",
]
if dimension == 2:
self.config.instance_set_path = "sigmoid_2D3M_train.csv"
self.config.test_set_path = "sigmoid_2D3M_test.csv"
self.config.discrete = [3, 3]
cfg_space = CS.ConfigurationSpace()
dim1 = CSH.UniformIntegerHyperparameter(
name="value_dim_1", lower=0, upper=2
)
dim2 = CSH.UniformIntegerHyperparameter(
name="value_dim_2", lower=0, upper=2
)
cfg_space.add(dim1)
cfg_space.add(dim2)
self.config.config_space = cfg_space
self.config.benchmark_info["state_description"] = [
"Remaining Budget",
"Shift (dimension 1)",
"Slope (dimension 1)",
"Shift (dimension 2)",
"Slope (dimension 2)",
"Action dim 1",
"Action dim 2",
]
if dimension == 3:
self.config.instance_set_path = "sigmoid_3D3M_train.csv"
self.config.test_set_path = "sigmoid_3D3M_test.csv"
self.config.discrete = [3, 3, 3]
cfg_space = CS.ConfigurationSpace()
dim1 = CSH.UniformIntegerHyperparameter(
name="value_dim_1", lower=0, upper=2
)
dim2 = CSH.UniformIntegerHyperparameter(
name="value_dim_2", lower=0, upper=2
)
dim3 = CSH.UniformIntegerHyperparameter(
name="value_dim_3", lower=0, upper=2
)
cfg_space.add(dim1)
cfg_space.add(dim2)
cfg_space.add(dim3)
self.config.config_space = cfg_space
self.config.benchmark_info["state_description"] = [
"Remaining Budget",
"Shift (dimension 1)",
"Slope (dimension 1)",
"Shift (dimension 2)",
"Slope (dimension 2)",
"Shift (dimension 3)",
"Slope (dimension 3)",
"Action 1",
"Action 2",
"Action 3",
]
if dimension == 5:
self.config.instance_set_path = "sigmoid_5D3M_train.csv"
self.config.test_set_path = "sigmoid_5D3M_test.csv"
self.config.discrete = [3, 3, 3, 3, 3]
cfg_space = CS.ConfigurationSpace()
dim1 = CSH.UniformIntegerHyperparameter(
name="value_dim_1", lower=0, upper=2
)
dim2 = CSH.UniformIntegerHyperparameter(
name="value_dim_2", lower=0, upper=2
)
dim3 = CSH.UniformIntegerHyperparameter(
name="value_dim_3", lower=0, upper=2
)
dim4 = CSH.UniformIntegerHyperparameter(
name="value_dim_4", lower=0, upper=2
)
dim5 = CSH.UniformIntegerHyperparameter(
name="value_dim_5", lower=0, upper=2
)
cfg_space.add(dim1)
cfg_space.add(dim2)
cfg_space.add(dim3)
cfg_space.add(dim4)
cfg_space.add(dim5)
self.config.config_space = cfg_space
self.config.benchmark_info["state_description"] = [
"Remaining Budget",
"Shift (dimension 1)",
"Slope (dimension 1)",
"Shift (dimension 2)",
"Slope (dimension 2)",
"Shift (dimension 3)",
"Slope (dimension 3)",
"Shift (dimension 4)",
"Slope (dimension 4)",
"Shift (dimension 5)",
"Slope (dimension 5)",
"Action 1",
"Action 2",
"Action 3",
"Action 4",
"Action 5",
]
self.config.seed = seed
self.read_instance_set()
self.read_instance_set(test=True)
return FunctionApproximationEnv(self.config)