Source code for prefsampling.point.ball

from __future__ import annotations

import warnings
from collections.abc import Iterable, Callable

import numpy as np

from prefsampling.inputvalidators import validate_int
from prefsampling.point.utils import validate_center_point, validate_width


[docs] def ball_uniform( num_points: int, num_dimensions: int, center_point: Iterable[float] = None, widths: float | Iterable[float] = 1, only_envelope: bool = False, seed: int = None, ) -> np.ndarray: """ Samples points uniformly at random in a ball. This function can also handle spheres and spheres of different width per dimensions, leading to distributions that are not really balls. For instance, you can obtain an ellipse by providing two different widths in the 2D case. Parameters ---------- num_points: int The number of points to sample. num_dimensions: int The number of dimensions for the ball. center_point: Iterable[float] The coordinates of the center point of the ball. It needs to have one coordinate per dimension. widths: float | Iterable[float], default: :code:`1` The width of the ball. If a single value is given, the width is applied to all dimensions. In case multiple values are given, they are applied to each dimension independently. only_envelope: bool, default: :code:`False` If set to :code:`True` only points on the envelope of the ball (the corresponding sphere) are sampled, and not in the inside. seed : int, default: :code:`None` Seed for numpy random number generator. Returns ------- np.ndarray The coordinates of the :code:`num_points` points that have been sampled. Validation ---------- .. image:: ../validation_plots/point/ball-uniform.png :width: 800 :alt: Observed frequencies for a uniform ball model """ validate_int(num_points, "num_points", 0) validate_int(num_dimensions, "num_dimensions", 1) center_point = validate_center_point(center_point, num_dimensions) widths = validate_width(widths, num_dimensions) rng = np.random.default_rng(seed) random_directions = rng.normal(size=(num_dimensions, num_points)) random_directions /= np.linalg.norm(random_directions, axis=0) if only_envelope: random_radii = 1.0 else: random_radii = rng.random(num_points) ** (1 / num_dimensions) return center_point + (widths / 2) * (random_directions * random_radii).T
[docs] def sphere_uniform( num_points: int, num_dimensions: int, center_point: Iterable[float] = None, widths: float | Iterable[float] = 1, seed: int = None, ) -> np.ndarray: """ Samples points uniformly at random in a sphere, that is, in the envelope of a ball. This is simply a shortcut of the :py:func:`~prefsampling.point.ball_uniform` with :code:`only_envelope = True`. Parameters ---------- num_points: int The number of points to sample. num_dimensions: int The number of dimensions for the ball. center_point: Iterable[float] The coordinates of the center point of the ball. It needs to have one coordinate per dimension. widths: float | Iterable[float], default: :code:`1` The width of the ball. If a single value is given, the width is applied to all dimensions. In case multiple values are given, they are applied to each dimension independently. seed : int, default: :code:`None` Seed for numpy random number generator. Returns ------- np.ndarray The coordinates of the :code:`num_points` points that have been sampled. Validation ---------- .. image:: ../validation_plots/point/sphere-uniform.png :width: 800 :alt: Observed frequencies for a uniform sphere model """ return ball_uniform( num_points, num_dimensions, center_point, widths, only_envelope=True, seed=seed )
[docs] def ball_resampling( num_points: int, num_dimensions: int, inner_sampler: Callable, inner_sampler_args: dict, center_point: Iterable[float] = None, width: float = 1, max_numer_resampling: int = 1000, seed: int = None, ) -> np.ndarray: """ Uses another point sampler and reject all points that do not fall inside the ball described as parameter. Be careful, if not set properly, this can run until the end of time (if always resampling for instance). Parameters ---------- num_points: int The number of points to sample. num_dimensions: int The number of dimensions for the ball. inner_sampler: Callable The function used to sample points before resampling. This function should accept an optional :code:`seed` parameter. Other parameters should be passed by name via the :code:`inner_sampler_args` argument. inner_sampler_args: dict The arguments passed to the `inner_sampler`. center_point: Iterable[float] The coordinates of the center point of the ball. It needs to have one coordinate per dimension. width: float, default: :code:`1` The width of the ball. Can only be a single value (as opposed to :py:func:`~prefsampling.point.ball.ball_uniform`). max_numer_resampling : int, default: :code:`1000` The maximum number of resampling. If exceeded, the center point is used. seed : int, default: :code:`None` Seed for numpy random number generator. We increase the seed by one each time we resample (to avoid always resampling the same point). Returns ------- np.ndarray The coordinates of the :code:`num_points` points that have been sampled. Validation ---------- .. image:: ../validation_plots/point/ball-resampling.png :width: 800 :alt: Observed frequencies for a uniform ball model """ validate_int(num_points, "num_points", 0) validate_int(num_dimensions, "num_dimensions", 1) center_point = validate_center_point(center_point, num_dimensions) if seed is not None: inner_sampler_args["seed"] = seed points = [] for _ in range(num_points): point = inner_sampler(**inner_sampler_args) if isinstance(point, Iterable): point = np.array(point, dtype=float) else: point = np.array([point], dtype=float) if len(point) != num_dimensions: raise ValueError( f"The inner sampler did not return a point with the suitable number " f"of dimensions ({num_dimensions} needed but {len(point)} returned)." ) num_resampling = 0 while np.linalg.norm(point - center_point) > (width / 2): if seed is not None: seed += 1 inner_sampler_args["seed"] = seed point = inner_sampler(**inner_sampler_args) num_resampling += 1 if num_resampling == max_numer_resampling: warnings.warn( "Too many resampling attempt for the resampling_ball. We used the center point " "instead.", RuntimeWarning, ) point = center_point break points.append(point) return np.array(points, dtype=float)