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

41 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-09-29 10:17 +0000

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

2 

3import importlib 

4import inspect 

5import re 

6from typing import Callable 

7 

8from sparkle.types.sparkle_callable import SparkleCallable 

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

10from sparkle.types.status import SolverStatus 

11from sparkle.types import objective 

12from sparkle.types.objective import SparkleObjective, UseTime 

13 

14 

15objective_string_regex = re.compile( 

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

17) 

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

19 

20 

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

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

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

24 

25 

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

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

28 

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

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

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

32 or just recorded. 

33 

34 Order of resolving: 

35 class_name of user defined SparkleObjectives 

36 class_name of sparkle defined SparkleObjectives 

37 default SparkleObjective with minimization unless specified as max 

38 

39 Args: 

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

41 

42 Returns: 

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

44 """ 

45 match = objective_string_regex.fullmatch(objective_name) 

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

47 return None 

48 

49 name = match.group("name") 

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

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

52 

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

54 name_options = [ 

55 (name, None), 

56 ] # Options of names to check for 

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

58 argument = int(m.group()) 

59 name_options = [ 

60 (name[: m.start()], argument), 

61 ] + name_options # Prepend 

62 

63 # First try to resolve the user input classes 

64 for rname, rarg in name_options: 

65 try: 

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

67 for o_name, o_class in inspect.getmembers( 

68 user_module, predicate=_check_class 

69 ): 

70 if o_name == rname: 

71 if rarg is not None: 

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

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

74 except Exception: 

75 pass 

76 

77 for rname, rarg in name_options: 

78 # Try to match with specially defined classes 

79 for o_name, o_class in inspect.getmembers(objective, predicate=_check_class): 

80 if o_name == rname: 

81 if rarg is not None: 

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

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

84 

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

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