Coverage for moptipy/evaluation/mo_selector.py: 100%
26 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-18 11:24 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-18 11:24 +0000
1"""
2A tool for selecting multi-objective data.
4Different from :func:`~moptipy.evaluation.selector.select_consistent`, the
5function :func:`~moptipy.evaluation.mo_selector.mo_select_consistent` can
6deal with multi-objective data.
7The difference is that such data may contain multiple results per run.
8"""
11from operator import itemgetter
12from typing import Iterable, TypeVar
14from pycommons.types import type_error
16from moptipy.evaluation.base import PerRunData
17from moptipy.evaluation.selector import (
18 SELECTOR_MAX_RUNS,
19 Selector,
20 select_consistent,
21)
23#: the type variable for the selector routine
24T = TypeVar("T", bound=PerRunData)
27def mo_select_consistent(
28 data: Iterable[T],
29 selectors: Iterable[Selector] = SELECTOR_MAX_RUNS) -> list[T]:
30 """
31 Select a consistent subset of data which may be multi-objective.
33 :param data: The input data stream
34 :param selectors: the selectors that should be used
35 :return: the selected data
37 >>> def __p(x) -> str:
38 ... return (f"{x.algorithm}/{x.instance}/{x.objective}/{x.encoding}/"
39 ... f"{x.rand_seed}")
41 >>> from moptipy.evaluation.selector import SELECTOR_SAME_SEEDS_FOR_ALL
43 >>> a1i1o1e1s1 = PerRunData("a1", "i1", "o1", "e1", 1)
44 >>> a1i1o1e1s2 = PerRunData("a1", "i1", "o1", "e1", 2)
45 >>> a1i1o1e1s3 = PerRunData("a1", "i1", "o1", "e1", 3)
46 >>> a1i2o1e1s1 = PerRunData("a1", "i2", "o1", "e1", 1)
47 >>> a1i2o1e1s2 = PerRunData("a1", "i2", "o1", "e1", 2)
48 >>> a1i2o1e1s3 = PerRunData("a1", "i2", "o1", "e1", 3)
49 >>> a2i1o1e1s1 = PerRunData("a2", "i1", "o1", "e1", 1)
50 >>> a2i1o1e1s2 = PerRunData("a2", "i1", "o1", "e1", 2)
51 >>> a2i1o1e1s3 = PerRunData("a2", "i1", "o1", "e1", 3)
52 >>> a2i2o1e1s1 = PerRunData("a1", "i2", "o1", "e1", 1)
53 >>> a2i2o1e1s2 = PerRunData("a2", "i2", "o1", "e1", 2)
54 >>> a2i2o1e1s3 = PerRunData("a2", "i2", "o1", "e1", 3)
56 >>> list(map(__p, mo_select_consistent((
57 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s3, a1i2o1e1s2,
58 ... a1i2o1e1s1, a1i2o1e1s2, a1i2o1e1s3,
59 ... a2i1o1e1s1, a2i1o1e1s2, a1i1o1e1s1,
60 ... a2i2o1e1s2, a2i2o1e1s3))))
61 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i2/o1/e1/2', \
62'a1/i2/o1/e1/2', 'a1/i2/o1/e1/3', 'a2/i1/o1/e1/1', 'a2/i1/o1/e1/2', \
63'a2/i2/o1/e1/2', 'a2/i2/o1/e1/3']
65 >>> list(map(__p, mo_select_consistent((
66 ... a1i1o1e1s2, a1i1o1e1s3, a1i1o1e1s3, a1i2o1e1s1,
67 ... a1i2o1e1s1, a1i2o1e1s2, a1i2o1e1s3, a1i2o1e1s1,
68 ... a2i1o1e1s1, a2i1o1e1s2,
69 ... a2i2o1e1s2, a2i2o1e1s3))))
70 ['a1/i1/o1/e1/2', 'a1/i2/o1/e1/3', 'a2/i1/o1/e1/2', 'a2/i2/o1/e1/3']
72 >>> list(map(__p, mo_select_consistent((
73 ... a1i1o1e1s2, a1i1o1e1s3, a1i1o1e1s3, a1i1o1e1s3,
74 ... a1i2o1e1s1, a1i2o1e1s2, a1i2o1e1s3,
75 ... a2i1o1e1s1, a2i1o1e1s2, a1i1o1e1s3,
76 ... a2i2o1e1s2))))
77 ['a1/i1/o1/e1/2', 'a1/i2/o1/e1/2', 'a2/i1/o1/e1/2', 'a2/i2/o1/e1/2']
79 >>> list(map(__p, mo_select_consistent((
80 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s3, a1i1o1e1s2, a1i1o1e1s2,
81 ... a2i2o1e1s1, a2i2o1e1s2, a2i2o1e1s3))))
82 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i1/o1/e1/2', 'a1/i1/o1/e1/2', \
83'a1/i1/o1/e1/3']
85 >>> list(map(__p, mo_select_consistent((
86 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s3, a2i1o1e1s2, a2i1o1e1s2,
87 ... a2i1o1e1s1, a2i1o1e1s2, a2i1o1e1s3))))
88 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i1/o1/e1/3', 'a2/i1/o1/e1/1', \
89'a2/i1/o1/e1/2', 'a2/i1/o1/e1/2', 'a2/i1/o1/e1/2', 'a2/i1/o1/e1/3']
91 >>> list(map(__p, mo_select_consistent((
92 ... a1i1o1e1s1, a1i1o1e1s1, a1i1o1e1s2, a1i2o1e1s2, a1i2o1e1s3))))
93 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i2/o1/e1/2', \
94'a1/i2/o1/e1/3']
96 >>> list(map(__p, mo_select_consistent((
97 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s2, a1i2o1e1s2))))
98 ['a1/i1/o1/e1/1', 'a1/i2/o1/e1/2']
100 >>> list(map(__p, mo_select_consistent((
101 ... a1i1o1e1s1, a1i1o1e1s1, a1i1o1e1s1, a1i1o1e1s1, a2i1o1e1s2))))
102 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/1', 'a1/i1/o1/e1/1', 'a1/i1/o1/e1/1']
104 >>> list(map(__p, mo_select_consistent((
105 ... a1i1o1e1s1, a2i1o1e1s2, a2i1o1e1s3))))
106 ['a2/i1/o1/e1/2', 'a2/i1/o1/e1/3']
108 >>> list(map(__p, mo_select_consistent((
109 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s3,
110 ... a1i2o1e1s1, a1i2o1e1s2, a1i2o1e1s3,
111 ... a2i1o1e1s1, a2i1o1e1s2,
112 ... a2i2o1e1s2, a2i2o1e1s3), SELECTOR_SAME_SEEDS_FOR_ALL)))
113 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i2/o1/e1/2', 'a1/i2/o1/e1/3', \
114'a2/i1/o1/e1/1', 'a2/i1/o1/e1/2', 'a2/i2/o1/e1/2', 'a2/i2/o1/e1/3']
116 >>> list(map(__p, mo_select_consistent((
117 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s3,
118 ... a1i2o1e1s1, a1i2o1e1s2, a1i2o1e1s3,
119 ... a2i1o1e1s1, a2i1o1e1s2,
120 ... a2i2o1e1s2, ), SELECTOR_SAME_SEEDS_FOR_ALL)))
121 ['a1/i1/o1/e1/1', 'a1/i1/o1/e1/2', 'a1/i2/o1/e1/2', \
122'a2/i1/o1/e1/1', 'a2/i1/o1/e1/2', 'a2/i2/o1/e1/2']
124 >>> try:
125 ... mo_select_consistent(1)
126 ... except TypeError as te:
127 ... print(te)
128 data should be an instance of typing.Iterable but is int, namely 1.
130 >>> try:
131 ... mo_select_consistent({234})
132 ... except TypeError as te:
133 ... print(str(te)[:20])
134 data[0] should be an
136 >>> try:
137 ... mo_select_consistent((a1i1o1e1s1, a1i1o1e1s2, a1i2o1e1s2), 1)
138 ... except TypeError as te:
139 ... print(str(te)[:19])
140 selectors should be
142 >>> try:
143 ... mo_select_consistent((a1i1o1e1s1, a1i1o1e1s2, a1i2o1e1s2), (1, ))
144 ... except TypeError as te:
145 ... print(str(te)[:21])
146 selectors[0] should b
147 """
148 if not isinstance(data, Iterable):
149 raise type_error(data, "data", Iterable)
150 if not isinstance(selectors, Iterable):
151 raise type_error(selectors, "selectors", Iterable)
153 mo_map: dict[tuple[str, str, str, str | None, int], list[T]] = {}
154 for i, item in enumerate(data):
155 if not isinstance(item, PerRunData):
156 raise type_error(item, f"data[{i}]", PerRunData)
157 key = (item.instance, item.algorithm, item.objective, item.encoding,
158 item.rand_seed)
159 if key in mo_map:
160 mo_map[key].append(item)
161 else:
162 mo_map[key] = [item]
164 raw: list[T] = select_consistent(map(
165 itemgetter(0), mo_map.values()), selectors)
166 result: list[T] = []
167 for item in raw:
168 key = (item.instance, item.algorithm, item.objective, item.encoding,
169 item.rand_seed)
170 result.extend(mo_map[key])
171 result.sort()
172 return result