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
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 10:17 +0000
1"""This package provides types for Sparkle applications."""
3import importlib
4import inspect
5import re
6from typing import Callable
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
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+)$")
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)
26def resolve_objective(objective_name: str) -> SparkleObjective:
27 """Try to resolve the objective class by (case-sensitive) name.
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.
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
39 Args:
40 objective_name: The name of the objective class. Can include parameter value k.
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
49 name = match.group("name")
50 minimise = not match.group("direction") == "max" # .group returns "" if no match
51 metric = match.group("type") == "metric"
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
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
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)
85 # No special objects found. Return objective with full name
86 return SparkleObjective(name=objective_name, minimise=minimise, metric=metric)