@@ -154,15 +154,77 @@ def search(self, coalition: np.ndarray) -> float:
154154 validity = np .apply_along_axis (self ._is_valid , axis = 1 , arr = temp_random_sample )
155155 filtered_samples = temp_random_sample [validity ]
156156
157+ if coalition .any ():
158+ filtered_samples = []
159+
157160 if len (filtered_samples ) < 0.05 * len (temp_random_sample ): # pragma: no cover
158161 logger .warning (
159162 "WARNING: Due to blinding less than 5% of the samples in the random search remain valid. "
160163 "Consider increasing the sampling budget of the random search." ,
161164 )
162165
163166 # predict performance values with the help of the surrogate model for the filtered configurations
164- vals : np .ndarray = np .array (self .explanation_task .get_single_surrogate_model ().evaluate (filtered_samples ))
167+ if len (filtered_samples ) > 0 :
168+ vals : np .ndarray = np .array (
169+ self .explanation_task .get_single_surrogate_model ().evaluate (filtered_samples ),
170+ )
171+ else :
172+ logger .warning (
173+ "WARNING: After filtering for conditions, no configurations were left, thus, using baseline value." ,
174+ )
175+ vals = np .array ([self .search (np .array ([False ] * len (coalition )))])
165176 else :
166177 vals : np .ndarray = np .array (self .explanation_task .get_single_surrogate_model ().evaluate (temp_random_sample ))
167178
168- return evaluate_aggregation (self .mode , vals )
179+ # determine the final, aggregated value of the coalition
180+ res = evaluate_aggregation (self .mode , vals )
181+
182+ # in case we are maximizing or minimizing, ensure that the value function is monotone
183+ if self .mode in (Aggregation .MAX , Aggregation .MIN ):
184+ res = self ._ensure_monotonicity (coalition , res )
185+
186+ # cache the coalition's value
187+ self .coalition_cache [str (coalition .tolist ())] = res
188+
189+ return res
190+
191+ def _ensure_monotonicity (self , coalition : np .ndarray , value : float ) -> float :
192+ """Ensure that the value function is monotonically increasing/decreasing depending on whether we want to maximize or minimize respectively.
193+
194+ Args:
195+ coalition: The current coalition.
196+ value: The value of the coalition as determined by searching.
197+
198+ Returns: The monotonicity-ensured value of the coalition.
199+
200+ """
201+ monotone_value = value
202+ checked_one = False
203+
204+ for i in range (len (coalition )):
205+ if coalition [i ]: # check whether the entry is True and set it to False to check for a cached result
206+ temp_coalition = coalition .copy ()
207+ temp_coalition [i ] = False
208+ if str (temp_coalition .tolist ()) in self .coalition_cache :
209+ checked_one = True
210+ monotone_value = evaluate_aggregation (
211+ self .mode ,
212+ np .array ([
213+ monotone_value ,
214+ self .coalition_cache [str (temp_coalition .tolist ())],
215+ ]),
216+ )
217+
218+ if not checked_one and coalition .any ():
219+ logger .warning (
220+ "Could not ensure monotonicity as none of the coalitions with one player less has been cached so far." ,
221+ )
222+
223+ if value < monotone_value :
224+ logger .debug (
225+ "Ensured monotonicity with a sub-coalition's value. Increased the value of the current coalition from %s to %s." ,
226+ value ,
227+ monotone_value ,
228+ )
229+
230+ return monotone_value
0 commit comments