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

1""" 

2A tool for selecting multi-objective data. 

3 

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""" 

9 

10 

11from operator import itemgetter 

12from typing import Iterable, TypeVar 

13 

14from pycommons.types import type_error 

15 

16from moptipy.evaluation.base import PerRunData 

17from moptipy.evaluation.selector import ( 

18 SELECTOR_MAX_RUNS, 

19 Selector, 

20 select_consistent, 

21) 

22 

23#: the type variable for the selector routine 

24T = TypeVar("T", bound=PerRunData) 

25 

26 

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. 

32 

33 :param data: The input data stream 

34 :param selectors: the selectors that should be used 

35 :return: the selected data 

36 

37 >>> def __p(x) -> str: 

38 ... return (f"{x.algorithm}/{x.instance}/{x.objective}/{x.encoding}/" 

39 ... f"{x.rand_seed}") 

40 

41 >>> from moptipy.evaluation.selector import SELECTOR_SAME_SEEDS_FOR_ALL 

42 

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) 

55 

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'] 

64 

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'] 

71 

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'] 

78 

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'] 

84 

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'] 

90 

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'] 

95 

96 >>> list(map(__p, mo_select_consistent(( 

97 ... a1i1o1e1s1, a1i1o1e1s2, a1i1o1e1s2, a1i2o1e1s2)))) 

98 ['a1/i1/o1/e1/1', 'a1/i2/o1/e1/2'] 

99 

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'] 

103 

104 >>> list(map(__p, mo_select_consistent(( 

105 ... a1i1o1e1s1, a2i1o1e1s2, a2i1o1e1s3)))) 

106 ['a2/i1/o1/e1/2', 'a2/i1/o1/e1/3'] 

107 

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'] 

115 

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'] 

123 

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. 

129 

130 >>> try: 

131 ... mo_select_consistent({234}) 

132 ... except TypeError as te: 

133 ... print(str(te)[:20]) 

134 data[0] should be an 

135 

136 >>> try: 

137 ... mo_select_consistent((a1i1o1e1s1, a1i1o1e1s2, a1i2o1e1s2), 1) 

138 ... except TypeError as te: 

139 ... print(str(te)[:19]) 

140 selectors should be 

141 

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) 

152 

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] 

163 

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