Coverage for sparkle/types/__init__.py: 88%

41 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-07 15:22 +0000

1"""This package provides types for Sparkle applications.""" 

2import importlib 

3import inspect 

4import re 

5from typing import Callable 

6 

7from sparkle.types.sparkle_callable import SparkleCallable 

8from sparkle.types.features import FeatureGroup, FeatureSubgroup, FeatureType 

9from sparkle.types.status import SolverStatus 

10from sparkle.types import objective 

11from sparkle.types.objective import SparkleObjective, UseTime 

12 

13 

14objective_string_regex = re.compile( 

15 r"(?P<name>[\w\-_]+)(:(?P<direction>min|max))?(:(?P<type>metric|objective))?$") 

16objective_variable_regex = re.compile(r"(-?\d+)$") 

17 

18 

19def _check_class(candidate: Callable) -> bool: 

20 """Verify whether a loaded class is a valid objective class.""" 

21 return inspect.isclass(candidate) and issubclass(candidate, SparkleObjective) 

22 

23 

24def resolve_objective(objective_name: str) -> SparkleObjective: 

25 """Try to resolve the objective class by (case-sensitive) name. 

26 

27 convention: objective_name(variable-k)?(:[min|max])?(:[metric|objective])? 

28 Here, min|max refers to the minimisation or maximisation of the objective 

29 and metric|objective refers to whether the objective should be optimized 

30 or just recorded. 

31 

32 Order of resolving: 

33 class_name of user defined SparkleObjectives 

34 class_name of sparkle defined SparkleObjectives 

35 default SparkleObjective with minimization unless specified as max 

36 

37 Args: 

38 name: The name of the objective class. Can include parameter value k. 

39 

40 Returns: 

41 Instance of the Objective class or None if not found. 

42 """ 

43 match = objective_string_regex.fullmatch(objective_name) 

44 if match is None or objective_name == "" or not objective_name[0].isalpha(): 

45 return None 

46 

47 name = match.group("name") 

48 minimise = not match.group("direction") == "max" # .group returns "" if no match 

49 metric = match.group("type") == "metric" 

50 

51 # Search for optional variable and record split point between name and variable 

52 name_options = [(name, None), ] # Options of names to check for 

53 if m := objective_variable_regex.search(name): 

54 argument = int(m.group()) 

55 name_options = [(name[:m.start()], argument), ] + name_options # Prepend 

56 

57 # First try to resolve the user input classes 

58 for rname, rarg in name_options: 

59 try: 

60 user_module = importlib.import_module("Settings.objective") 

61 for o_name, o_class in inspect.getmembers(user_module, 

62 predicate=_check_class): 

63 if o_name == rname: 

64 if rarg is not None: 

65 return o_class(rarg, minimise=minimise, metric=metric) 

66 return o_class(minimise=minimise, metric=metric) 

67 except Exception: 

68 pass 

69 

70 for rname, rarg in name_options: 

71 # Try to match with specially defined classes 

72 for o_name, o_class in inspect.getmembers(objective, 

73 predicate=_check_class): 

74 if o_name == rname: 

75 if rarg is not None: 

76 return o_class(rarg, minimise=minimise, metric=metric) 

77 return o_class(minimise=minimise, metric=metric) 

78 

79 # No special objects found. Return objective with full name 

80 return SparkleObjective(name=objective_name, minimise=minimise, metric=metric)