• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mmschlk / shapiq / 18490684955

14 Oct 2025 08:39AM UTC coverage: 93.111% (-0.7%) from 93.845%
18490684955

Pull #430

github

web-flow
Merge f75cf495e into ddda4a730
Pull Request #430: Enhance type safety and fix bugs across the codebase

304 of 360 new or added lines in 51 files covered. (84.44%)

12 existing lines in 9 files now uncovered.

4987 of 5356 relevant lines covered (93.11%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/src/shapiq/approximator/sampling.py
1
"""This module contains stochastic sampling procedures for coalitions of players."""
2

3
from __future__ import annotations
1✔
4

5
import copy
1✔
6
import warnings
1✔
7
from typing import TYPE_CHECKING
1✔
8

9
import numpy as np
1✔
10
from scipy.special import binom
1✔
11

12
from shapiq.utils.sets import powerset
1✔
13

14
if TYPE_CHECKING:
1✔
NEW
15
    from shapiq.typing import BoolVector, CoalitionTuple, FloatVector, IntVector
×
16

17

18
class CoalitionSampler:
1✔
19
    """Coalition Sampler for handling coalition sampling in approximation methods.
20

21
    The coalition sampler to generate a collection of subsets as a basis for approximation
22
    methods. Sampling is based on a more general variant of `Fumagalli et al. (2023) <https://doi.org/10.48550/arXiv.2303.01179>`_.
23
    The empty and grand coalition are always prioritized, and sampling budget is required ``>=2``.
24
    All variables are stored in the sampler, no objects are returned. The following variables
25
    are computed:
26
        - ``sampled_coalitions_matrix``: A binary matrix that consists of one row for each sampled
27
            coalition. Each row is a binary vector that indicates the players in the coalition.
28
            The matrix is of shape ``(n_coalitions, n_players)``.
29
        - ``sampled_coalitions_counter``: An array with the number of occurrences of the coalitions
30
            in the sampling process. The array is of shape ``(n_coalitions,)``.
31
        - ``sampled_coalitions_probability``: An array with the coalition probabilities according to
32
            the sampling procedure (i.e., the sampling weights). The array is of shape
33
            ``(n_coalitions,)``.
34
        - ``coalitions_per_size``: An array with the number of sampled coalitions per size
35
            (including the empty and full set). The array is of shape ``(n_players + 1,)``.
36
        - ``is_coalition_size_sampled``: An array that contains True, if the coalition size was
37
            sampled and False (computed exactly) otherwise. The array is of shape
38
            ``(n_players + 1,)``.
39
        - ``sampled_coalitions_dict``:`` A dictionary containing all sampled coalitions mapping to
40
            their number of occurrences. The dictionary is of type ``dict[tuple[int, ...], int]``.
41

42
    Attributes:
43
        n: The number of players in the game.
44

45
        n_max_coalitions: The maximum number of possible coalitions.
46

47
        adjusted_sampling_weights: The adjusted sampling weights without zero-weighted coalition sizes.
48
            The array is of shape ``(n_sizes_to_sample,)``.
49

50
        _rng: The random number generator used for sampling.
51

52

53
    Properties:
54
        sampled: A flag indicating whether the sampling process has been executed.
55

56
        coalitions_matrix: The binary matrix of sampled coalitions of shape ``(n_coalitions,
57
            n_players)``.
58

59
        coalitions_counter: The number of occurrences of the coalitions. The array is of shape
60
            ``(n_coalitions,)``.
61

62
        coalitions_probability: The coalition probabilities according to the sampling procedure. The
63
             array is of shape ``(n_coalitions,)``.
64

65
        coalitions_size_probability: The coalitions size probabilities according to the sampling
66
            procedure. The array is of shape ``(n_coalitions,)``.
67

68
        coalitions_size_probability: The coalitions probabilities in their size according to the
69
            sampling procedure. The array is of shape ``(n_coalitions,)``.
70

71
        n_coalitions: The number of coalitions that have been sampled.
72

73
        sampling_adjustment_weights: The weights that account for the sampling procedure (importance
74
            sampling)
75

76
        sampling_size_probabilities: The probabilities of each coalition size to be sampled.
77

78
    Examples:
79
        >>> sampler = CoalitionSampler(n_players=3, sampling_weights=np.array([1, 0.5, 0.5, 1]))
80
        >>> sampler.sample(5)
81
        >>> print(sampler.coalitions_matrix)
82
        [[False, False, False],
83
         [False, False, True],
84
         [True, True, True],
85
         [True, False, False],
86
         [False, True, True]]
87

88
    """
89

90
    def __init__(
1✔
91
        self,
92
        n_players: int,
93
        sampling_weights: np.ndarray,
94
        *,
95
        pairing_trick: bool = False,
96
        random_state: int | None = None,
97
    ) -> None:
98
        """Initialize the coalition sampler.
99

100
        Args:
101
            n_players: The number of players in the game.
102

103
            sampling_weights: Sampling for weights for coalition sizes, must be non-negative and at
104
                least one ``>0``. The sampling weights for size ``0`` and ``n`` are ignored, as
105
                these are always sampled.
106

107
            pairing_trick: Samples each coalition jointly with its complement. Defaults to
108
                ``False``.
109

110
            random_state: The random state to use for the sampling process. Defaults to ``None``.
111
        """
112
        self.pairing_trick: bool = pairing_trick
1✔
113

114
        # set sampling weights
115
        if not (sampling_weights >= 0).all():  # Check non-negativity of sampling weights
1✔
116
            msg = "All sampling weights must be non-negative"
1✔
117
            raise ValueError(msg)
1✔
118
        self._sampling_weights = sampling_weights / np.sum(sampling_weights)  # make probabilities
1✔
119

120
        # raise warning if sampling weights are not symmetric but pairing trick is activated
121
        if self.pairing_trick and not np.allclose(
1✔
122
            self._sampling_weights,
123
            self._sampling_weights[::-1],
124
        ):
125
            warnings.warn(
1✔
126
                UserWarning(
127
                    "Pairing trick is activated, but sampling weights are not symmetric. "
128
                    "This may lead to unexpected results.",
129
                ),
130
                stacklevel=2,
131
            )
132

133
        # set player numbers
134
        if n_players + 1 != np.size(sampling_weights):  # shape of sampling weights -> sizes 0,...,n
1✔
135
            msg = (
1✔
136
                f"{n_players} elements must correspond to {n_players + 1} coalition sizes "
137
                "(including empty subsets)"
138
            )
139
            raise ValueError(msg)
1✔
140
        self.n: int = n_players
1✔
141
        self.n_max_coalitions = int(2**self.n)
1✔
142
        self.n_max_coalitions_per_size = np.array([binom(self.n, k) for k in range(self.n + 1)])
1✔
143

144
        # set random state
145
        self._rng: np.random.Generator = np.random.default_rng(seed=random_state)
1✔
146

147
        # set variables for sampling and exclude coalition sizes with zero weight
148
        self._coalitions_to_exclude: list[int] = []
1✔
149
        for size, weight in enumerate(self._sampling_weights):
1✔
150
            if weight == 0 and 0 < size < self.n:
1✔
151
                self.n_max_coalitions -= int(binom(self.n, size))
1✔
152
                self._coalitions_to_exclude.extend([size])
1✔
153
        self.adjusted_sampling_weights: FloatVector = np.array([])
1✔
154

155
        # set sample size variables (for border trick)
156
        self._coalitions_to_compute: list[int] = []  # coalitions to compute
1✔
157
        self._coalitions_to_sample: list[int] = []  # coalitions to sample
1✔
158

159
        # initialize variables to be computed and stored
160
        self.sampled_coalitions_dict: dict[CoalitionTuple, int] = {}
1✔
161
        self.coalitions_per_size: IntVector = np.array([], dtype=int)
1✔
162

163
        # variables accessible through properties
164
        # coalitions
165
        self._sampled_coalitions_matrix: BoolVector = np.array([], dtype=bool)
1✔
166
        # coalitions counter
167
        self._sampled_coalitions_counter: IntVector = np.array([], dtype=int)
1✔
168
        # coalitions size probability
169
        self._sampled_coalitions_size_prob: FloatVector = np.array([], dtype=float)
1✔
170
        # coalitions in size probability
171
        self._sampled_coalitions_in_size_prob: FloatVector = np.array([], dtype=float)
1✔
172
        # coalition size sampled
173
        self._is_coalition_size_sampled: BoolVector = np.array([], dtype=bool)
1✔
174

175
    @property
1✔
176
    def n_coalitions(self) -> int:
1✔
177
        """Returns the number of coalitions that have been sampled.
178

179
        Returns:
180
            The number of coalitions that have been sampled.
181

182
        """
183
        try:
1✔
184
            return int(self._sampled_coalitions_matrix.shape[0])
1✔
UNCOV
185
        except AttributeError:  # if not sampled
×
UNCOV
186
            return 0
×
187

188
    @property
1✔
189
    def is_coalition_size_sampled(self) -> np.ndarray:
1✔
190
        """Returns a Boolean array indicating whether the coalition size was sampled.
191

192
        Returns:
193
            The Boolean array whether the coalition size was sampled.
194

195
        """
196
        return copy.deepcopy(self._is_coalition_size_sampled)
1✔
197

198
    @property
1✔
199
    def is_coalition_sampled(self) -> np.ndarray:
1✔
200
        """Returns a Boolean array indicating whether the coalition was sampled.
201

202
        Returns:
203
            The Boolean array whether the coalition was sampled.
204

205
        """
206
        coalitions_size = np.sum(self.coalitions_matrix, axis=1)
1✔
207
        return self._is_coalition_size_sampled[coalitions_size]
1✔
208

209
    @property
1✔
210
    def sampling_adjustment_weights(self) -> np.ndarray:
1✔
211
        """Returns the weights that account for the sampling procedure.
212

213
        Returns:
214
            An array with adjusted weight for each coalition
215

216
        """
217
        coalitions_counter = self.coalitions_counter
1✔
218
        is_coalition_sampled = self.is_coalition_sampled
1✔
219
        # Number of coalitions sampled
220

221
        n_total_samples = np.sum(coalitions_counter[is_coalition_sampled])
1✔
222
        # Helper array for computed and sampled coalitions
223
        total_samples_values = np.array([1, n_total_samples])
1✔
224
        # Create array per coalition and the total samples values, or 1, if computed
225
        n_coalitions_total_samples = total_samples_values[is_coalition_sampled.astype(int)]
1✔
226
        # Create array with the adjusted weights
227
        return self.coalitions_counter / (self.coalitions_probability * n_coalitions_total_samples)
1✔
228

229
    @property
1✔
230
    def coalitions_matrix(self) -> np.ndarray:
1✔
231
        """Returns the binary matrix of sampled coalitions.
232

233
        Returns:
234
            A copy of the sampled coalitions matrix as a binary matrix of shape (n_coalitions,
235
                n_players).
236

237
        """
238
        return copy.deepcopy(self._sampled_coalitions_matrix)
1✔
239

240
    @property
1✔
241
    def sampling_size_probabilities(self) -> np.ndarray:
1✔
242
        """Returns the probabilities of sampling a coalition size.
243

244
        Returns:
245
            An array containing the probabilities of shappe ``(n+1,)``
246

247
        """
248
        size_probs = np.zeros(self.n + 1)
1✔
249
        size_probs[self._coalitions_to_sample] = self.adjusted_sampling_weights / np.sum(
1✔
250
            self.adjusted_sampling_weights,
251
        )
252
        return size_probs
1✔
253

254
    @property
1✔
255
    def coalitions_counter(self) -> np.ndarray:
1✔
256
        """Returns the number of occurrences of the coalitions.
257

258
        Returns:
259
            A copy of the sampled coalitions counter of shape ``(n_coalitions,)``.
260

261
        """
262
        return copy.deepcopy(self._sampled_coalitions_counter)
1✔
263

264
    @property
1✔
265
    def coalitions_probability(self) -> np.ndarray:
1✔
266
        """Returns the coalition probabilities according to the sampling procedure.
267

268
        Returns the coalition probabilities according to the sampling procedure. The coalitions'
269
        probability is calculated as the product of the probability of the size of the coalition
270
        times the probability of the coalition in that size.
271

272
        Returns:
273
            A copy of the sampled coalitions probabilities of shape ``(n_coalitions,)`` or ``None``
274
                if the coalition probabilities are not available.
275

276
        """
277
        return self._sampled_coalitions_size_prob * self._sampled_coalitions_in_size_prob
1✔
278

279
    @property
1✔
280
    def coalitions_size_probability(self) -> np.ndarray:
1✔
281
        """Returns the probabilities of the coalition sizes according to the sampling procedure.
282

283
        Returns:
284
            A copy of the probabilities of shape (n_coalitions,).
285

286
        """
287
        return copy.deepcopy(self._sampled_coalitions_size_prob)
1✔
288

289
    @property
1✔
290
    def coalitions_in_size_probability(self) -> np.ndarray:
1✔
291
        """Return probabilities per coalition size.
292

293
        Returns the probabilities of the coalition in the corresponding coalition size according
294
        to the sampling.
295

296
        Note:
297
            With uniform sampling, this is always ``1/binom(n,coalition_size)``.
298

299
        Returns:
300
            A copy of the sampled probabilities of shape ``(n_coalitions,)``.
301

302
        """
303
        return copy.deepcopy(self._sampled_coalitions_in_size_prob)
1✔
304

305
    @property
1✔
306
    def coalitions_size(self) -> np.ndarray:
1✔
307
        """Returns the coalition sizes of the sampled coalitions.
308

309
        Returns:
310
            The coalition sizes of the sampled coalitions.
311

312
        """
313
        return np.sum(self.coalitions_matrix, axis=1)
1✔
314

315
    @property
1✔
316
    def empty_coalition_index(self) -> int | None:
1✔
317
        """Returns the index of the empty coalition.
318

319
        Returns:
320
            The index of the empty coalition or ``None`` if the empty coalition was not sampled.
321

322
        """
323
        try:
1✔
324
            if self.coalitions_per_size[0] >= 1:
1✔
325
                return int(np.where(self.coalitions_size == 0)[0][0])
1✔
326
        except IndexError:
1✔
327
            pass
1✔
328
        return None
1✔
329

330
    def set_random_state(self, random_state: int | None = None) -> None:
1✔
331
        """Set the random state for the coalition sampler.
332

333
        Args:
334
            random_state: The random state to set. If ``None``, no random state is set. Defaults to
335
                ``None``.
336

337
        """
338
        self._rng = np.random.default_rng(seed=random_state)
1✔
339

340
    def execute_border_trick(self, sampling_budget: int) -> int:
1✔
341
        """Execute the border trick for a sampling budget.
342

343
        Moves coalition sizes from coalitions_to_sample to coalitions_to_compute, if the expected
344
        number of coalitions is higher than the total number of coalitions of that size. The border
345
        trick is based on a more general version of `Fumagalli et al. (2023) <https://doi.org/10.48550/arXiv.2303.01179>`_.
346

347
        Args:
348
            sampling_budget: The number of coalitions to sample.
349

350
        Returns:
351
            The sampling budget reduced by the number of coalitions in ``coalitions_to_compute``.
352

353
        """
354
        coalitions_per_size = np.array([binom(self.n, k) for k in range(self.n + 1)])
1✔
355
        expected_number_of_coalitions = sampling_budget * self.adjusted_sampling_weights
1✔
356
        sampling_exceeds_expectation = (
1✔
357
            expected_number_of_coalitions >= coalitions_per_size[self._coalitions_to_sample]
358
        )
359
        while sampling_exceeds_expectation.any():
1✔
360
            coalitions_to_move = [
1✔
361
                self._coalitions_to_sample[index]
362
                for index, include in enumerate(sampling_exceeds_expectation)
363
                if include
364
            ]
365
            self._coalitions_to_compute.extend(
1✔
366
                [
367
                    self._coalitions_to_sample.pop(self._coalitions_to_sample.index(move_this))
368
                    for move_this in coalitions_to_move
369
                ],
370
            )
371
            sampling_budget -= int(np.sum(coalitions_per_size[coalitions_to_move]))
1✔
372
            self.adjusted_sampling_weights = self.adjusted_sampling_weights[
1✔
373
                ~sampling_exceeds_expectation
374
            ] / np.sum(self.adjusted_sampling_weights[~sampling_exceeds_expectation])
375
            expected_number_of_coalitions = sampling_budget * self.adjusted_sampling_weights
1✔
376
            sampling_exceeds_expectation = (
1✔
377
                expected_number_of_coalitions >= coalitions_per_size[self._coalitions_to_sample]
378
            )
379
        return sampling_budget
1✔
380

381
    def execute_pairing_trick(self, sampling_budget: int, coalition_tuple: tuple[int, ...]) -> int:
1✔
382
        """Executes the pairing-trick for a sampling budget and coalition sizes.
383

384
        The pairing-trick is based on the idea by `Covert and Lee (2021) <https://doi.org/10.48550/arXiv.2012.01536>`_
385
        and pairs each coalition with its complement.
386

387
        Args:
388
            sampling_budget: The currently remaining sampling budget.
389
            coalition_tuple: The coalition to pair with its complement.
390

391
        Returns:
392
            The remaining sampling budget after the pairing-trick.
393

394
        """
395
        coalition_size = len(coalition_tuple)
1✔
396
        paired_coalition_size = self.n - coalition_size
1✔
397
        if paired_coalition_size in self._coalitions_to_sample:
1✔
398
            paired_coalition_indices = list(set(range(self.n)) - set(coalition_tuple))
1✔
399
            paired_coalition_tuple = tuple(sorted(paired_coalition_indices))
1✔
400
            self.coalitions_per_size[paired_coalition_size] += 1
1✔
401
            # adjust coalitions counter using the paired coalition
402
            try:  # if coalition is not new
1✔
403
                self.sampled_coalitions_dict[paired_coalition_tuple] += 1
1✔
404
            except KeyError:  # if coalition is new
1✔
405
                self.sampled_coalitions_dict[paired_coalition_tuple] = 1
1✔
406
                sampling_budget -= 1
1✔
407
        return sampling_budget
1✔
408

409
    def _reset_variables(self, sampling_budget: int) -> None:
1✔
410
        """Resets the variables of the sampler at each sampling call.
411

412
        Args:
413
            sampling_budget: The budget for the approximation (i.e., the number of distinct
414
                coalitions to sample/evaluate).
415

416
        """
417
        self.sampled_coalitions_dict = {}
1✔
418
        self.coalitions_per_size = np.zeros(self.n + 1, dtype=int)
1✔
419
        self._is_coalition_size_sampled = np.zeros(self.n + 1, dtype=bool)
1✔
420
        self._sampled_coalitions_counter = np.zeros(sampling_budget, dtype=int)
1✔
421
        self._sampled_coalitions_matrix = np.zeros((sampling_budget, self.n), dtype=bool)
1✔
422
        self._sampled_coalitions_size_prob = np.zeros(sampling_budget, dtype=float)
1✔
423
        self._sampled_coalitions_in_size_prob = np.zeros(sampling_budget, dtype=float)
1✔
424

425
        self._coalitions_to_compute = []
1✔
426
        self._coalitions_to_sample = [
1✔
427
            coalition_size
428
            for coalition_size in range(self.n + 1)
429
            if coalition_size not in self._coalitions_to_exclude
430
        ]
431
        self.adjusted_sampling_weights = copy.deepcopy(
1✔
432
            self._sampling_weights[self._coalitions_to_sample],
433
        )
434
        self.adjusted_sampling_weights /= np.sum(self.adjusted_sampling_weights)  # probability
1✔
435

436
    def execute_empty_grand_coalition(self, sampling_budget: int) -> int:
1✔
437
        """Sets the empty and grand coalition to be computed.
438

439
        Ensures empty and grand coalition are prioritized and computed independent of
440
        the sampling weights. Works similar to border-trick but only with empty and grand coalition.
441

442
        Args:
443
            sampling_budget: The budget for the approximation (i.e., the number of distinct
444
                coalitions to sample/evaluate).
445

446
        Returns:
447
            The remaining sampling budget, i.e. reduced by ``2``.
448

449
        """
450
        empty_grand_coalition_indicator = np.zeros_like(self.adjusted_sampling_weights, dtype=bool)
1✔
451
        empty_grand_coalition_size = [0, self.n]
1✔
452
        empty_grand_coalition_index = [
1✔
453
            self._coalitions_to_sample.index(size) for size in empty_grand_coalition_size
454
        ]
455
        empty_grand_coalition_indicator[empty_grand_coalition_index] = True
1✔
456
        coalitions_to_move = [
1✔
457
            self._coalitions_to_sample[index]
458
            for index, include in enumerate(empty_grand_coalition_indicator)
459
            if include
460
        ]
461
        self._coalitions_to_compute.extend(
1✔
462
            [
463
                self._coalitions_to_sample.pop(self._coalitions_to_sample.index(move_this))
464
                for move_this in coalitions_to_move
465
            ],
466
        )
467
        self.adjusted_sampling_weights = self.adjusted_sampling_weights[
1✔
468
            ~empty_grand_coalition_indicator
469
        ] / np.sum(self.adjusted_sampling_weights[~empty_grand_coalition_indicator])
470
        sampling_budget -= 2
1✔
471
        return sampling_budget
1✔
472

473
    def sample(self, sampling_budget: int) -> None:
1✔
474
        """Samples distinct coalitions according to the specified budget.
475

476
        The empty and grand coalition are always prioritized, and sampling budget is required ``>=2``.
477

478
        Args:
479
            sampling_budget: The budget for the approximation (i.e., the number of distinct
480
                coalitions to sample/evaluate).
481

482
        Raises:
483
            UserWarning: If the sampling budget is higher than the maximum number of coalitions.
484

485
        """
486
        if sampling_budget < 2:
1✔
487
            # Empty and grand coalition always have to be computed.
488
            msg = "A minimum sampling budget of 2 samples is required."
1✔
489
            raise ValueError(msg)
1✔
490

491
        if sampling_budget > self.n_max_coalitions:
1✔
492
            warnings.warn("Not all budget is required due to the border-trick.", stacklevel=2)
1✔
493
            sampling_budget = min(sampling_budget, self.n_max_coalitions)  # set budget to max coals
1✔
494

495
        self._reset_variables(sampling_budget)
1✔
496

497
        # Prioritize empty and grand coalition
498
        sampling_budget = self.execute_empty_grand_coalition(sampling_budget)
1✔
499

500
        # Border-Trick: enumerate all coalitions, where the expected number of coalitions exceeds
501
        # the total number of coalitions of that size (i.e. binom(n_players, coalition_size))
502
        sampling_budget = self.execute_border_trick(sampling_budget)
1✔
503

504
        # Sort by size for esthetics
505
        self._coalitions_to_compute.sort(key=self._sort_coalitions)
1✔
506

507
        # raise warning if budget is higher than 90% of samples remaining to be sampled
508
        n_samples_remaining = np.sum([binom(self.n, size) for size in self._coalitions_to_sample])
1✔
509
        if sampling_budget > 0.9 * n_samples_remaining:
1✔
510
            warnings.warn(
1✔
511
                UserWarning(
512
                    "Sampling might be inefficient (stalls) due to the sampling budget being close "
513
                    "to the total number of coalitions to be sampled.",
514
                ),
515
                stacklevel=2,
516
            )
517

518
        # sample coalitions
519
        if len(self._coalitions_to_sample) > 0:
1✔
520
            iteration_counter = 0  # stores the number of samples drawn (duplicates included)
1✔
521
            while sampling_budget > 0:
1✔
522
                iteration_counter += 1
1✔
523

524
                # draw coalition
525
                coalition_size = self._rng.choice(
1✔
526
                    self._coalitions_to_sample,
527
                    size=1,
528
                    p=self.adjusted_sampling_weights,
529
                )[0]
530
                ids = self._rng.choice(self.n, size=coalition_size, replace=False)
1✔
531
                coalition_tuple = tuple(sorted(ids))  # get coalition
1✔
532
                self.coalitions_per_size[coalition_size] += 1
1✔
533

534
                # add coalition
535
                try:  # if coalition is not new
1✔
536
                    self.sampled_coalitions_dict[coalition_tuple] += 1
1✔
537
                except KeyError:  # if coalition is new
1✔
538
                    self.sampled_coalitions_dict[coalition_tuple] = 1
1✔
539
                    sampling_budget -= 1
1✔
540

541
                # execute pairing-trick by including the complement
542
                if self.pairing_trick and sampling_budget > 0:
1✔
543
                    sampling_budget = self.execute_pairing_trick(sampling_budget, coalition_tuple)
1✔
544

545
        # convert coalition counts to the output format
546
        coalition_index = 0
1✔
547
        # add all coalitions that are computed exhaustively
548
        for coalition_size in self._coalitions_to_compute:
1✔
549
            self.coalitions_per_size[coalition_size] = int(binom(self.n, coalition_size))
1✔
550
            for coalition in powerset(
1✔
551
                range(self.n),
552
                min_size=coalition_size,
553
                max_size=coalition_size,
554
            ):
555
                self._sampled_coalitions_matrix[coalition_index, list(coalition)] = 1
1✔
556
                self._sampled_coalitions_counter[coalition_index] = 1
1✔
557
                self._sampled_coalitions_size_prob[coalition_index] = 1  # weight is set to 1
1✔
558
                self._sampled_coalitions_in_size_prob[coalition_index] = 1  # weight is set to 1
1✔
559
                coalition_index += 1
1✔
560
        # add all coalitions that are sampled
561
        for coalition_tuple, count in self.sampled_coalitions_dict.items():
1✔
562
            self._sampled_coalitions_matrix[coalition_index, list(coalition_tuple)] = 1
1✔
563
            self._sampled_coalitions_counter[coalition_index] = count
1✔
564
            # probability of the sampled coalition, i.e. sampling weight (for size) divided by
565
            # number of coalitions of that size
566
            self._sampled_coalitions_size_prob[coalition_index] = self.adjusted_sampling_weights[
1✔
567
                self._coalitions_to_sample.index(len(coalition_tuple))
568
            ]
569
            self._sampled_coalitions_in_size_prob[coalition_index] = (
1✔
570
                1 / self.n_max_coalitions_per_size[len(coalition_tuple)]
571
            )
572
            coalition_index += 1
1✔
573

574
        # set the flag to indicate that these sizes are sampled
575
        for coalition_size in self._coalitions_to_sample:
1✔
576
            self._is_coalition_size_sampled[coalition_size] = True
1✔
577

578
    def _sort_coalitions(self, value: int) -> float:
1✔
579
        """Used to sort coalition sizes by distance to center, i.e. grand coalition and emptyset first.
580

581
        Args:
582
            value: The size of the coalition.
583

584
        Returns:
585
            The negative distance to the center n/2
586

587
        """
588
        # Sort by distance to center
589
        return -abs(self.n / 2 - value)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc