|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -from enum import Enum |
| 3 | +from collections.abc import Callable |
4 | 4 |
|
5 | 5 | import numpy as np |
6 | 6 |
|
7 | 7 | from prefsampling.inputvalidators import validate_num_voters_candidates |
8 | 8 |
|
9 | 9 |
|
10 | | -class EuclideanSpace(Enum): |
11 | | - """ |
12 | | - Constants used to represent Euclidean spaces |
13 | | - """ |
14 | | - |
15 | | - UNIFORM = "Uniform Space" |
16 | | - """ |
17 | | - Uniform space |
18 | | - """ |
19 | | - GAUSSIAN = "Gaussian Space" |
20 | | - """ |
21 | | - Gaussian space |
22 | | - """ |
23 | | - SPHERE = "Spherical Space" |
24 | | - """ |
25 | | - Spherical space |
26 | | - """ |
27 | | - BALL = "Ball Space" |
28 | | - """ |
29 | | - Ball-shaped space |
30 | | - """ |
31 | | - |
32 | | - |
33 | 10 | @validate_num_voters_candidates |
34 | | -def election_positions( |
| 11 | +def sample_election_positions( |
35 | 12 | num_voters: int, |
36 | 13 | num_candidates: int, |
37 | | - space: EuclideanSpace, |
38 | | - dimension: int, |
39 | | - rng: np.random.Generator, |
40 | | -) -> (np.ndarray, np.ndarray): |
| 14 | + point_sampler: Callable, |
| 15 | + point_sampler_args: dict, |
| 16 | + candidate_point_sampler: Callable = None, |
| 17 | + candidate_point_sampler_args: dict = None, |
| 18 | + seed: int = None, |
| 19 | +) -> tuple[np.ndarray, np.ndarray]: |
41 | 20 | """ |
42 | | - Returns the position of the voters and the candidates in a Euclidean space. |
43 | 21 |
|
44 | 22 | Parameters |
45 | 23 | ---------- |
46 | | - num_voters: int |
47 | | - The number of voters. |
48 | | - num_candidates: int |
49 | | - The number of candidates. |
50 | | - space : :py:class:`~prefsampling.core.euclidean.EuclideanSpace` |
51 | | - Type of space considered. Should be a constant defined in the |
52 | | - :py:class:`~prefsampling.core.euclidean.EuclideanSpace`. |
53 | | - dimension : int |
54 | | - Number of dimensions for the space considered. |
55 | | - rng : np.random.Generator |
56 | | - The numpy generator to use for randomness. |
| 24 | + num_voters : int |
| 25 | + Number of Voters. |
| 26 | + num_candidates : int |
| 27 | + Number of Candidates. |
| 28 | + point_sampler : Callable |
| 29 | + The sampler used to sample point in the space. Used for both voters and candidates |
| 30 | + unless a `candidate_space` is provided. |
| 31 | + point_sampler_args : dict |
| 32 | + The arguments passed to the `point_sampler`. The argument `num_points` is ignored |
| 33 | + and replaced by the number of voters or candidates. |
| 34 | + candidate_point_sampler : Callable, default: :code:`None` |
| 35 | + The sampler used to sample the points of the candidates. If a value is provided, |
| 36 | + then the `space` argument is only used for voters. |
| 37 | + candidate_point_sampler_args : dict |
| 38 | + The arguments passed to the `candidate_point_sampler`. The argument `num_points` |
| 39 | + is ignored and replaced by the number of candidates. |
| 40 | + seed : int, default: :code:`None` |
| 41 | + Seed for numpy random number generator. Also passed to the point samplers if |
| 42 | + a value is provided. |
57 | 43 |
|
58 | 44 | Returns |
59 | 45 | ------- |
60 | | - (np.ndarray, np.ndarray) |
61 | | - The position of the voters and of the candidates respectively. |
62 | | - """ |
63 | | - if isinstance(space, Enum): |
64 | | - space = EuclideanSpace(space.value) |
65 | | - else: |
66 | | - space = EuclideanSpace(space) |
67 | | - if space == EuclideanSpace.UNIFORM: |
68 | | - voters = rng.random((num_voters, dimension)) |
69 | | - candidates = rng.random((num_candidates, dimension)) |
70 | | - elif space == EuclideanSpace.GAUSSIAN: |
71 | | - voters = rng.normal(loc=0.5, scale=0.15, size=(num_voters, dimension)) |
72 | | - candidates = rng.normal(loc=0.5, scale=0.15, size=(num_candidates, dimension)) |
73 | | - elif space == EuclideanSpace.SPHERE: |
74 | | - voters = np.array( |
75 | | - [list(random_sphere(dimension, rng)[0]) for _ in range(num_voters)] |
76 | | - ) |
77 | | - candidates = np.array( |
78 | | - [list(random_sphere(dimension, rng)[0]) for _ in range(num_candidates)] |
79 | | - ) |
80 | | - elif space == EuclideanSpace.BALL: |
81 | | - voters = np.array( |
82 | | - [list(random_ball(dimension, rng)[0]) for _ in range(num_voters)] |
83 | | - ) |
84 | | - candidates = np.array( |
85 | | - [list(random_ball(dimension, rng)[0]) for _ in range(num_candidates)] |
86 | | - ) |
87 | | - else: |
88 | | - raise ValueError( |
89 | | - "The `space` argument needs to be one of the constant defined in the " |
90 | | - "core.euclidean.EuclideanSpace enumeration. Choices are: " |
91 | | - + ", ".join(str(s) for s in EuclideanSpace) |
92 | | - ) |
93 | | - return voters, candidates |
94 | | - |
| 46 | + tuple[np.ndarray, np.ndarray] |
| 47 | + The positions of the voters and of the candidates. |
95 | 48 |
|
96 | | -def random_ball( |
97 | | - dimension: int, rng: np.random.Generator, num_points: int = 1, radius: float = 1 |
98 | | -) -> np.ndarray: |
99 | | - random_directions = rng.normal(size=(dimension, num_points)) |
100 | | - random_directions /= np.linalg.norm(random_directions, axis=0) |
101 | | - random_radii = rng.random(num_points) ** (1 / dimension) |
102 | | - x = radius * (random_directions * random_radii).T |
103 | | - return x |
| 49 | + """ |
| 50 | + if candidate_point_sampler is not None and candidate_point_sampler_args is None: |
| 51 | + raise ValueError("If candidate_point_sampler is not None, a value needs to be " |
| 52 | + "passed to candidate_point_sampler_args (even if it's just " |
| 53 | + "an empty dictionary).") |
104 | 54 |
|
| 55 | + if seed is not None: |
| 56 | + point_sampler_args["seed"] = seed |
| 57 | + if candidate_point_sampler is not None: |
| 58 | + candidate_point_sampler_args["seed"] = seed |
105 | 59 |
|
106 | | -def random_sphere( |
107 | | - dimension: int, rng: np.random.Generator, num_points: int = 1, radius: float = 1 |
108 | | -) -> np.ndarray: |
109 | | - random_directions = rng.normal(size=(dimension, num_points)) |
110 | | - random_directions /= np.linalg.norm(random_directions, axis=0) |
111 | | - random_radii = 1.0 |
112 | | - return radius * (random_directions * random_radii).T |
| 60 | + point_sampler_args['num_points'] = num_voters |
| 61 | + voters_pos = point_sampler(**point_sampler_args) |
| 62 | + dimension = len(voters_pos[0]) |
| 63 | + if candidate_point_sampler is None: |
| 64 | + point_sampler_args['num_points'] = num_candidates |
| 65 | + candidates_pos = point_sampler(**point_sampler_args) |
| 66 | + else: |
| 67 | + candidate_point_sampler_args['num_points'] = num_candidates |
| 68 | + candidates_pos = candidate_point_sampler(**candidate_point_sampler_args) |
| 69 | + if len(candidates_pos[0]) != dimension: |
| 70 | + raise ValueError("The position of the voters and of the candidates do not have the " |
| 71 | + "same dimension. Use different point samplers to solve this " |
| 72 | + "problem.") |
| 73 | + return voters_pos, candidates_pos |
0 commit comments