def build_from(self, cmd_args): """Create a definition of the problem's search space, using information from the user's script configuration (if provided) and command line arguments. :param cmd_args: A list of command line arguments provided for the user's script. :rtype: `metaopt.algo.space.Space` .. note:: A template configuration file complementing user's script can be provided either by explicitly using the prefix '--config=' or by being the first positional argument. """ self.userargs_tmpl = None self.userconfig_tmpl = None self.space = Space() self.userconfig, self.is_userconfig_an_option = self._build_from_args( cmd_args) if self.userconfig: self._build_from_config(self.userconfig) log.debug( "Configuration and command line arguments were parsed and " "a `Space` object was built successfully:\n%s", self.space) return self.space
def test_interval(self): """Check whether interval is cool.""" space = Space() probs = (0.1, 0.2, 0.3, 0.4) categories = ('asdfa', 2, 3, 4) dim = Categorical('yolo', OrderedDict(zip(categories, probs)), shape=2) space.register(dim) dim = Integer('yolo2', 'uniform', -3, 6) space.register(dim) dim = Real('yolo3', 'norm', 0.9) space.register(dim) assert space.interval() == [categories, (-3, 3), (-np.inf, np.inf)]
def space(): """Construct a simple space with every possible kind of Dimension.""" space = Space() categories = {'asdfa': 0.1, 2: 0.2, 3: 0.3, 4: 0.4} dim = Categorical('yolo', categories, shape=2) space.register(dim) dim = Integer('yolo2', 'uniform', -3, 6) space.register(dim) dim = Real('yolo3', 'alpha', 0.9) space.register(dim) return space
def test_sample(self, seed): """Check whether sampling works correctly.""" space = Space() probs = (0.1, 0.2, 0.3, 0.4) categories = ('asdfa', 2, 3, 4) dim1 = Categorical('yolo', OrderedDict(zip(categories, probs)), shape=(2, 2)) space.register(dim1) dim2 = Integer('yolo2', 'uniform', -3, 6) space.register(dim2) dim3 = Real('yolo3', 'norm', 0.9) space.register(dim3) point = space.sample(seed=seed) test_point = [ (dim1.sample()[0], dim2.sample()[0], dim3.sample()[0]), ] assert len(point) == len(test_point) == 1 assert len(point[0]) == len(test_point[0]) == 3 assert np.all(point[0][0] == test_point[0][0]) assert point[0][1] == test_point[0][1] assert point[0][2] == test_point[0][2] points = space.sample(2, seed=seed) points1 = dim1.sample(2) points2 = dim2.sample(2) points3 = dim3.sample(2) test_points = [(points1[0], points2[0], points3[0]), (points1[1], points2[1], points3[1])] assert len(points) == len(test_points) == 2 for i in range(2): assert len(points[i]) == len(test_points[i]) == 3 assert np.all(points[i][0] == test_points[i][0]) assert points[i][1] == test_points[i][1] assert points[i][2] == test_points[i][2]
def test_repr(self): """Test str/repr.""" space = Space() dim = Integer('yolo2', 'uniform', -3, 6, shape=(2, )) space.register(dim) dim = Real('yolo3', 'norm', 0.9) space.register(dim) assert str(space) == "Space(["\ "Integer(name=yolo2, prior={uniform: (-3, 6), {}}, shape=(2,)),\n" \ " Real(name=yolo3, prior={norm: (0.9,), {}}, shape=())])"
def test_getitem(self): """Test getting dimensions from space.""" space = Space() probs = (0.1, 0.2, 0.3, 0.4) categories = ('asdfa', 2, 3, 4) dim = Categorical('yolo', OrderedDict(zip(categories, probs)), shape=2) space.register(dim) dim = Integer('yolo2', 'uniform', -3, 6) space.register(dim) dim = Real('yolo3', 'norm', 0.9) space.register(dim) assert space['yolo'].type == 'categorical' assert space[0].type == 'categorical' with pytest.raises(KeyError): space['asdf'] with pytest.raises(IndexError): space[3]
def test_register_and_contain(self): """Register bunch of dimensions, check if points/name are in space.""" space = Space() assert 'yolo' not in space assert (('asdfa', 2), 0, 3.5) not in space categories = {'asdfa': 0.1, 2: 0.2, 3: 0.3, 4: 0.4} dim = Categorical('yolo', categories, shape=2) space.register(dim) dim = Integer('yolo2', 'uniform', -3, 6) space.register(dim) dim = Real('yolo3', 'norm', 0.9) space.register(dim) assert 'yolo' in space assert 'yolo2' in space assert 'yolo3' in space assert (('asdfa', 2), 0, 3.5) in space assert (('asdfa', 2), 7, 3.5) not in space
def test_bad_setitem(self): """Check exceptions in setting items in Space.""" space = Space() # The name of an integer must be a of `str` type. # Integers are reversed for indexing the OrderedDict. with pytest.raises(TypeError) as exc: space[5] = Integer('yolo', 'uniform', -3, 6) assert "string" in str(exc.value) # Only object of type `Dimension` are allowed in `Space`. with pytest.raises(TypeError) as exc: space['ispis'] = 'nope' assert "Dimension" in str(exc.value) # Cannot register something with the same name. space.register(Integer('yolo', 'uniform', -3, 6)) with pytest.raises(ValueError) as exc: space.register(Real('yolo', 'uniform', 0, 6)) assert "another name" in str(exc.value)
def test_bad_contain(self): """Checking with no iterables does no good.""" space = Space() with pytest.raises(TypeError): 5 in space
def test_init(self): """Instantiate space, must be an ordered dictionary.""" space = Space() assert isinstance(space, OrderedDict)
class SpaceBuilder(object, metaclass=SingletonType): """Build a `Space` object form user's configuration.""" USERCONFIG_OPTION = '--config=' USERCONFIG_KEYWORD = 'mopt~' USERARGS_SEARCH = r'\W*([a-zA-Z0-9_-]+)~(.*)' USERARGS_TMPL = r'(.*)~(.*)' def __init__(self): """Initialize a `SpaceBuilder`.""" self.userconfig = None self.is_userconfig_an_option = None self.userargs_tmpl = None self.userconfig_tmpl = None self.dimbuilder = DimensionBuilder() self.space = None self.converter = None def build_from(self, cmd_args): """Create a definition of the problem's search space, using information from the user's script configuration (if provided) and command line arguments. :param cmd_args: A list of command line arguments provided for the user's script. :rtype: `metaopt.algo.space.Space` .. note:: A template configuration file complementing user's script can be provided either by explicitly using the prefix '--config=' or by being the first positional argument. """ self.userargs_tmpl = None self.userconfig_tmpl = None self.space = Space() self.userconfig, self.is_userconfig_an_option = self._build_from_args( cmd_args) if self.userconfig: self._build_from_config(self.userconfig) log.debug( "Configuration and command line arguments were parsed and " "a `Space` object was built successfully:\n%s", self.space) return self.space def _build_from_config(self, config_path): self.converter = infer_converter_from_file_type(config_path) self.userconfig_tmpl = self.converter.parse(config_path) stack = collections.deque() stack.append(('', self.userconfig_tmpl)) while True: try: namespace, stuff = stack.pop() except IndexError: break if isinstance(stuff, dict): for k, v in stuff.items(): stack.append(('/'.join([namespace, str(k)]), v)) elif isinstance(stuff, list): for position, thing in enumerate(stuff): stack.append(('/'.join([namespace, str(position)]), thing)) elif isinstance(stuff, str): if stuff.startswith(self.USERCONFIG_KEYWORD): dimension = self.dimbuilder.build( namespace, stuff[len(self.USERCONFIG_KEYWORD):]) try: self.space.register(dimension) except ValueError as exc: error_msg = "Conflict for name '{}' in script configuration "\ "and arguments.".format(namespace) raise ValueError(error_msg) from exc def _build_from_args(self, cmd_args): userconfig = None is_userconfig_an_option = None self.userargs_tmpl = collections.defaultdict(list) args_pattern = re.compile(self.USERARGS_SEARCH) args_prefix_pattern = re.compile(self.USERARGS_TMPL) for arg in cmd_args: found = args_pattern.findall(arg) if len(found) != 1: if arg.startswith(self.USERCONFIG_OPTION): if not userconfig: userconfig = arg[len(self.USERCONFIG_OPTION):] is_userconfig_an_option = True else: raise ValueError( "Already found one configuration file in: %s" % userconfig) else: self.userargs_tmpl[None].append(arg) continue name, expression = found[0] namespace = '/' + name dimension = self.dimbuilder.build(namespace, expression) self.space.register(dimension) found = args_prefix_pattern.findall(arg) assert len(found) == 1 and found[0][ 1] == expression, "Parsing prefix problem." self.userargs_tmpl[namespace] = found[0][0] + '=' if not userconfig and self.userargs_tmpl[ None]: # try the first positional argument if os.path.isfile(self.userargs_tmpl[None][0]): userconfig = self.userargs_tmpl[None].pop(0) is_userconfig_an_option = False return userconfig, is_userconfig_an_option def build_to(self, config_path, trial): """Use templates saved from `build_from` to generate a config file (if needed) and command line arguments to correspond to specific parameter selections. :param config_path: Path in which the configuration file instance will be created. :param trial: A `metaopt.core.worker.trial.Trial` object with concrete parameter values for the defined `Space`. :returns: A list with the command line arguments that must be given to script's execution. """ if self.userconfig: self._build_to_config(config_path, trial) return self._build_to_args(config_path, trial) def _build_to_config(self, config_path, trial): config_instance = copy.deepcopy(self.userconfig_tmpl) for param in trial.params: stuff = config_instance path = param.name.split('/') for key in path[1:]: # Parameter name may correspond to stuff in cmd args if isinstance(stuff, list): key = int(key) try: stuff[key] except IndexError: break else: # isinstance(stuff, dict): if key not in stuff: break if isinstance(stuff[key], str): stuff[key] = param.value else: stuff = stuff[key] self.converter.generate(config_path, config_instance) def _build_to_args(self, config_path, trial): cmd_args = [] if self.userconfig: if self.is_userconfig_an_option: cmd_args.append(self.USERCONFIG_OPTION + config_path) else: cmd_args.append(config_path) cmd_args.extend(self.userargs_tmpl[None]) for param in trial.params: if param.name not in self.userargs_tmpl: continue prefix = self.userargs_tmpl[param.name] cmd_args.append(prefix + str(param.value)) return cmd_args