|
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