From 9349864120d1905c567472bb7cbb8e79c94791e7 Mon Sep 17 00:00:00 2001 From: Simon Rey Date: Wed, 21 Feb 2024 12:07:41 +0100 Subject: [PATCH] Special handling of comb for python 3.7 --- prefsampling/approval/noise.py | 5 +++-- prefsampling/ordinal/groupseparable.py | 3 ++- prefsampling/tree/schroeder.py | 5 +++-- prefsampling/utils.py | 27 ++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 prefsampling/utils.py diff --git a/prefsampling/approval/noise.py b/prefsampling/approval/noise.py index ab7fdd2..add1b1e 100644 --- a/prefsampling/approval/noise.py +++ b/prefsampling/approval/noise.py @@ -6,6 +6,7 @@ import numpy as np from prefsampling.inputvalidators import validate_num_voters_candidates +from prefsampling.utils import comb class NoiseType(Enum): @@ -91,9 +92,9 @@ def noise( # Prepare buckets for x in range(len(A) + 1): - num_options_in = math.comb(len(A), x) + num_options_in = comb(len(A), x) for y in range(len(B) + 1): - num_options_out = math.comb(len(B), y) + num_options_out = comb(len(B), y) if noise_type == NoiseType.HAMMING: factor = phi ** (len(A) - x + y) diff --git a/prefsampling/ordinal/groupseparable.py b/prefsampling/ordinal/groupseparable.py index 61ebc58..262639b 100644 --- a/prefsampling/ordinal/groupseparable.py +++ b/prefsampling/ordinal/groupseparable.py @@ -14,6 +14,7 @@ from prefsampling.tree.caterpillar import caterpillar_tree from prefsampling.tree.balanced import balanced_tree from prefsampling.inputvalidators import validate_num_voters_candidates +from prefsampling.utils import comb class TreeSampler(Enum): @@ -161,7 +162,7 @@ def _number_group_separable_profiles(m: int, r: int, n: int) -> float: of internal nodes `r` based on the formula from `Karpov (2019) `_ """ - return math.comb(m - 1, r) * math.comb(m - 1 + r, m) * (2 ** (n - 1) - 1) ** (r - 1) + return comb(m - 1, r) * comb(m - 1 + r, m) * (2 ** (n - 1) - 1) ** (r - 1) def _sample_a_vote(node, reverse=False): diff --git a/prefsampling/tree/schroeder.py b/prefsampling/tree/schroeder.py index bd2ea2c..b69119e 100644 --- a/prefsampling/tree/schroeder.py +++ b/prefsampling/tree/schroeder.py @@ -7,6 +7,7 @@ from prefsampling.inputvalidators import validate_int from prefsampling.tree.node import Node +from prefsampling.utils import comb def validate_num_leaves_nodes(num_leaves: int, num_internal_nodes: int | None): @@ -52,8 +53,8 @@ def _random_num_internal_nodes(num_leaves: int, rng: np.random.Generator) -> int def _num_schroeder_tree(num_internal_nodes, num_leaves): return ( - math.comb(num_leaves - 1, num_internal_nodes) - * math.comb(num_leaves - 1 + num_internal_nodes, num_leaves) + comb(num_leaves - 1, num_internal_nodes) + * comb(num_leaves - 1 + num_internal_nodes, num_leaves) / (num_leaves - 1) ) diff --git a/prefsampling/utils.py b/prefsampling/utils.py new file mode 100644 index 0000000..cab10e8 --- /dev/null +++ b/prefsampling/utils.py @@ -0,0 +1,27 @@ +import math + + +def comb(n: int, k: int): + """ + Function to compute the binomial coefficient. It uses math.comb if available (i.e., if Python >= + 3.8), otherwise computes it by hand. + + Parameters + ---------- + n + k + + Returns + ------- + + """ + if hasattr(math, "comb"): + return math.comb(n, k) + return _comb(n, k) + + +def _comb(n, k): + try: + return math.factorial(n) // math.factorial(k) // math.factorial(n - k) + except ValueError: + return 0