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

mmschlk / shapiq / 18449788232

12 Oct 2025 09:32PM UTC coverage: 93.266% (-0.6%) from 93.845%
18449788232

Pull #430

github

web-flow
Merge 023d3b7ca into dede390c9
Pull Request #430: Enhance type safety and fix bugs across the codebase

278 of 326 new or added lines in 46 files covered. (85.28%)

12 existing lines in 9 files now uncovered.

4986 of 5346 relevant lines covered (93.27%)

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, CoalitionsLookup, 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: CoalitionsLookup = {}  # coal -> count
1✔
161
        self.coalitions_per_size: IntVector = np.array([], dtype=int)
1✔
162

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

496
        self._reset_variables(sampling_budget)
1✔
497

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

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

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

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

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

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

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

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

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

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

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

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

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

588
        """
589
        # Sort by distance to center
590
        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