Skip to content

Commit 27d5d06

Browse files
committed
Continuing updating the docs
1 parent 0aca76e commit 27d5d06

File tree

19 files changed

+3283
-4472
lines changed

19 files changed

+3283
-4472
lines changed

docs-source/source/reference/ordinal/euclidean.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ Euclidean Models
44
.. automodule:: prefsampling.ordinal.euclidean
55

66
.. autofunction:: prefsampling.ordinal.euclidean.euclidean
7+
8+
.. autoclass:: prefsampling.ordinal.euclidean.EuclideanSpace
9+
:members:

prefsampling/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
__author__ = "Simon Rey and Stanisław Szufa"
22
__email__ = "[email protected]"
3-
__version__ = "0.1.15"
3+
__version__ = "0.1.16"
44

55
from enum import Enum
66
from itertools import chain
77

88
from prefsampling.approval import SetDistance
99
from prefsampling.ordinal import TreeSampler
10+
from prefsampling.core.euclidean import EuclideanSpace
1011

1112

1213
class CONSTANTS(Enum):
1314
"""All constants of the package"""
1415

1516
_ignore_ = "member cls"
1617
cls = vars()
17-
for member in chain(list(TreeSampler), list(SetDistance)):
18+
for member in chain(list(TreeSampler), list(SetDistance), list(EuclideanSpace)):
1819
if member.name in cls:
1920
raise ValueError(
2021
f"The name {member.name} is used in more than one enumeration. The"

prefsampling/approval/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
euclidean_threshold,
1616
euclidean_vcr,
1717
euclidean_constant_size,
18+
EuclideanSpace,
1819
)
1920
from prefsampling.approval.truncated_ordinal import truncated_ordinal
2021
from prefsampling.approval.urn import urn, urn_constant_size, urn_partylist
@@ -34,6 +35,7 @@
3435
"euclidean_threshold",
3536
"euclidean_vcr",
3637
"euclidean_constant_size",
38+
"EuclideanSpace",
3739
"urn_partylist",
3840
"truncated_ordinal",
3941
"urn",

prefsampling/approval/euclidean.py

Lines changed: 103 additions & 129 deletions
Large diffs are not rendered by default.

prefsampling/core/euclidean.py

Lines changed: 99 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ class EuclideanSpace(Enum):
2222
UNBOUNDED_GAUSSIAN = "unbounded_gaussian"
2323

2424

25-
def euclidean_space_to_sampler(space: EuclideanSpace, num_dimensions: int):
25+
def euclidean_space_to_sampler(space: EuclideanSpace, num_dimensions: int) -> (Callable, dict):
26+
"""
27+
Returns the point sampler together with its arguments corresponding to the EuclideanSpace
28+
passed as argument.
29+
"""
2630
if space == EuclideanSpace.UNIFORM_BALL:
2731
return ball_uniform, {"num_dimensions": num_dimensions}
2832
if space == EuclideanSpace.UNIFORM_SPHERE:
@@ -52,45 +56,76 @@ def euclidean_space_to_sampler(space: EuclideanSpace, num_dimensions: int):
5256

5357
def _sample_points(
5458
num_points: int,
55-
sampler: Callable,
56-
sampler_args: dict,
57-
positions: Iterable[float],
59+
num_dimensions: int,
60+
positions: EuclideanSpace | Callable | Iterable[Iterable[float]],
61+
positions_args: dict,
5862
sampled_object_name: str,
5963
) -> np.ndarray:
60-
if positions is None:
61-
if sampler is None:
62-
raise ValueError(
63-
f"You need to either provide a sampler for the {sampled_object_name} "
64-
f"or their positions."
64+
"""
65+
Samples the points (if necessary) based on the input of the Euclidean function.
66+
"""
67+
if isinstance(positions, Iterable):
68+
try:
69+
positions = np.array(positions, dtype=float)
70+
except Exception as e:
71+
msg = (
72+
"When trying to cast the provided positions to a numpy array, the above "
73+
"exception occurred..."
6574
)
66-
if sampler_args is None:
67-
sampler_args = dict()
68-
sampler_args["num_points"] = num_points
69-
positions = sampler(**sampler_args)
70-
else:
71-
positions = np.array(positions, dtype=float)
72-
if len(positions) != num_points:
75+
raise Exception(msg) from e
76+
77+
if num_dimensions == 1:
78+
expected_shape = (num_points,)
79+
else:
80+
expected_shape = (num_points, num_dimensions)
81+
if positions.shape != expected_shape:
7382
raise ValueError(
74-
f"The provided number of points does not match the number of "
75-
f"{sampled_object_name} required ({len(positions)} points provided for"
76-
f"{num_points} {sampled_object_name}."
83+
f"The provided positions do not match the expected shape. Shape is "
84+
f"{positions.shape} while {expected_shape} was expected "
85+
f"(num_{sampled_object_name}, num_dimensions)."
86+
)
87+
return positions
88+
89+
if not isinstance(positions, Callable):
90+
try:
91+
if isinstance(positions, Enum):
92+
space = EuclideanSpace(positions.value)
93+
else:
94+
space = EuclideanSpace(positions)
95+
except Exception as e:
96+
msg = (
97+
f"If the positions for the {sampled_object_name} is not an Iterable (already, "
98+
f"given positions) or a Callable (a sampler), then it should be a "
99+
f"EuclideanSpace element. Casting the input to EuclideanSpace failed with the "
100+
f"above exception."
77101
)
102+
raise Exception(msg) from e
103+
104+
positions, new_positions_args = euclidean_space_to_sampler(space, num_dimensions)
105+
new_positions_args.update(positions_args)
106+
positions_args = new_positions_args
107+
positions_args["num_points"] = num_points
108+
positions = np.array(positions(**positions_args))
109+
110+
if positions.shape != (num_points, num_dimensions):
111+
raise ValueError(
112+
"After sampling the position, the obtained shape is not as expected. "
113+
f"Shape is {positions.shape} while {(num_points, num_dimensions)} was "
114+
f"expected (num_{sampled_object_name}, num_dimensions)."
115+
)
116+
78117
return positions
79118

80119

81120
@validate_num_voters_candidates
82121
def sample_election_positions(
83122
num_voters: int,
84123
num_candidates: int,
85-
euclidean_space: EuclideanSpace = None,
86-
candidate_euclidean_space: EuclideanSpace = None,
87-
num_dimensions: int = None,
88-
point_sampler: Callable = None,
89-
point_sampler_args: dict = None,
90-
candidate_point_sampler: Callable = None,
91-
candidate_point_sampler_args: dict = None,
92-
voters_positions: Iterable[Iterable[float]] = None,
93-
candidates_positions: Iterable[Iterable[float]] = None,
124+
num_dimensions: int,
125+
voters_positions: EuclideanSpace | Callable | Iterable[Iterable[float]],
126+
candidates_positions: EuclideanSpace | Callable | Iterable[Iterable[float]],
127+
voters_positions_args: dict = None,
128+
candidates_positions_args: dict = None,
94129
seed: int = None,
95130
) -> tuple[np.ndarray, np.ndarray]:
96131
"""
@@ -101,36 +136,32 @@ def sample_election_positions(
101136
Number of Voters.
102137
num_candidates : int
103138
Number of Candidates.
104-
euclidean_space: EuclideanSpace, default: :code:`None`
105-
Use a pre-defined Euclidean space for sampling the position of the voters. If no
106-
`candidate_euclidean_space` is provided, the value of 'euclidean_space' is used for the
107-
candidates as well. A number of dimension needs to be provided.
108-
candidate_euclidean_space: EuclideanSpace, default: :code:`None`
109-
Use a pre-defined Euclidean space for sampling the position of the candidates. If no
110-
value is provided, the value of 'euclidean_space' is used. A number of dimension needs
111-
to be provided.
112-
num_dimensions: int, default: :code:`None`
139+
num_dimensions: int
113140
The number of dimensions to use. Using this argument is mandatory when passing a space
114141
as argument. If you pass samplers as arguments and use the num_dimensions, then, the
115142
value of num_dimensions is passed as a kwarg to the samplers.
116-
point_sampler : Callable, default: :code:`None`
117-
The sampler used to sample point in the space. It should be a function accepting
118-
arguments 'num_points' and 'seed'. Used for both voters and candidates unless a
119-
`candidate_space` is provided.
120-
point_sampler_args : dict, default: :code:`None`
121-
The arguments passed to the `point_sampler`. The argument `num_points` is ignored
122-
and replaced by the number of voters or candidates.
123-
candidate_point_sampler : Callable, default: :code:`None`
124-
The sampler used to sample the points of the candidates. It should be a function
125-
accepting arguments 'num_points' and 'seed'. If a value is provided, then the
126-
`point_sampler_args` argument is only used for voters.
127-
candidate_point_sampler_args : dict
128-
The arguments passed to the `candidate_point_sampler`. The argument `num_points`
129-
is ignored and replaced by the number of candidates.
130-
voters_positions : Iterable[Iterable[float]]
131-
Position of the voters.
132-
candidates_positions : Iterable[Iterable[float]]
133-
Position of the candidates.
143+
voters_positions: py:class:`~prefsampling.core.euclidean.EuclideanSpace` | Callable | Iterable[Iterable[float]]
144+
The positions of the voters, or a way to determine them. If an Iterable is passed,
145+
then it is assumed to be the positions themselves. Otherwise, it is assumed that a
146+
sampler for the positions is passed. It can be either the nickname of a sampler---when
147+
passing a py:class:`~prefsampling.core.euclidean.EuclideanSpace`; or a sampler.
148+
A sampler is a function that takes as keywords arguments: 'num_points',
149+
'num_dimensions', and 'seed'. Additional arguments can be provided with by using the
150+
:code:`voters_positions_args` argument.
151+
candidates_positions: py:class:`~prefsampling.core.euclidean.EuclideanSpace` | Callable | Iterable[Iterable[float]]
152+
The positions of the candidates, or a way to determine them. If an Iterable is passed,
153+
then it is assumed to be the positions themselves. Otherwise, it is assumed that a
154+
sampler for the positions is passed. It can be either the nickname of a sampler---when
155+
passing a py:class:`~prefsampling.core.euclidean.EuclideanSpace`; or a sampler.
156+
A sampler is a function that takes as keywords arguments: 'num_points',
157+
'num_dimensions', and 'seed'. Additional arguments can be provided with by using the
158+
:code:`candidates_positions_args` argument.
159+
voters_positions_args: dict, default: :code:`dict()`
160+
Additional keyword arguments passed to the :code:`voters_positions` sampler when the
161+
latter is a Callable.
162+
candidates_positions_args: dict, default: :code:`dict()`
163+
Additional keyword arguments passed to the :code:`candidates_positions` sampler when the
164+
latter is a Callable.
134165
seed : int, default: :code:`None`
135166
Seed for numpy random number generator. Also passed to the point samplers if
136167
a value is provided.
@@ -141,66 +172,26 @@ def sample_election_positions(
141172
The positions of the voters and of the candidates.
142173
143174
"""
144-
if euclidean_space:
145-
if num_dimensions is None:
146-
raise ValueError(
147-
"If you are using the 'euclidean_space' argument, you need to also "
148-
"provide a number of dimensions."
149-
)
150-
validate_int(num_dimensions, "number of dimensions", 1)
151-
if isinstance(euclidean_space, Enum):
152-
euclidean_space = EuclideanSpace(euclidean_space.value)
153-
else:
154-
euclidean_space = EuclideanSpace(euclidean_space)
155-
156-
point_sampler, point_sampler_args = euclidean_space_to_sampler(
157-
euclidean_space, num_dimensions
158-
)
159-
if candidate_euclidean_space:
160-
if num_dimensions is None:
161-
raise ValueError(
162-
"If you are using the 'candidate_euclidean_space' argument, you need "
163-
"to also provide a number of dimensions."
164-
)
165-
if isinstance(candidate_euclidean_space, Enum):
166-
candidate_euclidean_space = EuclideanSpace(candidate_euclidean_space.value)
167-
else:
168-
candidate_euclidean_space = EuclideanSpace(candidate_euclidean_space)
169-
170-
candidate_point_sampler, candidate_point_sampler_args = (
171-
euclidean_space_to_sampler(candidate_euclidean_space, num_dimensions)
172-
)
173-
174-
if point_sampler_args is None:
175-
point_sampler_args = dict()
175+
validate_int(num_dimensions, lower_bound=0, value_descr="number of dimensions")
176+
if voters_positions_args is None:
177+
voters_positions_args = dict()
178+
if candidates_positions_args is None:
179+
candidates_positions_args = dict()
176180
if seed is not None:
177-
point_sampler_args["seed"] = seed
178-
if candidate_point_sampler is not None:
179-
candidate_point_sampler_args["seed"] = seed
180-
if num_dimensions is not None:
181-
point_sampler_args["num_dimensions"] = num_dimensions
182-
if candidate_point_sampler is not None:
183-
candidate_point_sampler_args["num_dimensions"] = num_dimensions
181+
voters_positions_args["seed"] = seed
182+
candidates_positions_args["seed"] = seed
183+
184+
voters_positions_args["num_dimensions"] = num_dimensions
185+
candidates_positions_args["num_dimensions"] = num_dimensions
184186

185187
voters_pos = _sample_points(
186-
num_voters, point_sampler, point_sampler_args, voters_positions, "voters"
188+
num_voters, num_dimensions, voters_positions, voters_positions_args, "voters"
187189
)
188-
dimension = len(voters_pos[0])
189-
if candidate_point_sampler:
190-
point_sampler = candidate_point_sampler
191-
point_sampler_args = candidate_point_sampler_args
192190
cand_pos = _sample_points(
193191
num_candidates,
194-
point_sampler,
195-
point_sampler_args,
192+
num_dimensions,
196193
candidates_positions,
194+
candidates_positions_args,
197195
"candidates",
198196
)
199-
200-
if len(cand_pos[0]) != dimension:
201-
raise ValueError(
202-
"The position of the voters and of the candidates do not have the same dimension ("
203-
f"{dimension} for the voters and {len(cand_pos[0])} for the candidates)."
204-
)
205-
206197
return voters_pos, cand_pos

prefsampling/ordinal/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
mallows,
2323
norm_mallows,
2424
)
25-
from prefsampling.ordinal.euclidean import euclidean
25+
from prefsampling.ordinal.euclidean import euclidean, EuclideanSpace
2626
from prefsampling.ordinal.plackettluce import plackett_luce
2727
from prefsampling.ordinal.groupseparable import group_separable, TreeSampler
2828
from prefsampling.ordinal.identity import identity
@@ -42,6 +42,7 @@
4242
"mallows",
4343
"norm_mallows",
4444
"euclidean",
45+
"EuclideanSpace",
4546
"plackett_luce",
4647
"group_separable",
4748
"TreeSampler",

0 commit comments

Comments
 (0)