Source code for hats.pixel_math.validators

from __future__ import annotations

from enum import Enum

import numpy as np

import hats.pixel_math.healpix_shim as hp


[docs] class ValidatorsErrors(str, Enum): """Error messages for the coordinate validators"""
[docs] INVALID_DEC = "declination must be in the -90.0 to 90.0 degree range"
[docs] INVALID_RADIUS = "cone radius must be positive"
[docs] INVALID_NUM_VERTICES = "polygon must contain a minimum of 3 vertices"
[docs] DUPLICATE_VERTICES = "polygon has duplicated vertices"
[docs] DEGENERATE_POLYGON = "polygon is degenerate"
[docs] INVALID_RADEC_RANGE = "invalid ra or dec range"
[docs] INVALID_COORDS_SHAPE = "invalid coordinates shape"
[docs] INVALID_CONCAVE_SHAPE = "polygon must be convex"
[docs] def validate_radius(radius_arcsec: float): """Validates that a cone search radius is positive Parameters ---------- radius_arcsec : float The cone radius, in arcseconds Raises ------ ValueError if radius is non-positive """ if radius_arcsec <= 0: raise ValueError(ValidatorsErrors.INVALID_RADIUS.value)
[docs] def validate_declination_values(dec: float | list[float]): """Validates that declination values are in the [-90,90] degree range Parameters ---------- dec : float | list[float] The declination values to be validated Raises ------ ValueError if declination values are not in the [-90,90] degree range """ dec_values = np.array(dec) lower_bound, upper_bound = -90.0, 90.0 if not np.all((dec_values >= lower_bound) & (dec_values <= upper_bound)): raise ValueError(ValidatorsErrors.INVALID_DEC.value)
[docs] def validate_polygon(vertices: list[tuple[float, float]]): """Checks if the polygon contain a minimum of three vertices, that they are unique and that the polygon does not fall on a great circle. Parameters ---------- vertices : list[tuple[float, float]] The list of vertice coordinates for the polygon, (ra, dec), in degrees. Raises ------ ValueError exception if the polygon is invalid. """ vertices = np.array(vertices) if vertices.shape[1] != 2: raise ValueError(ValidatorsErrors.INVALID_COORDS_SHAPE.value) _, dec = vertices.T validate_declination_values(dec) if len(vertices) < 3: raise ValueError(ValidatorsErrors.INVALID_NUM_VERTICES.value) if len(vertices) != len(np.unique(vertices, axis=0)): raise ValueError(ValidatorsErrors.DUPLICATE_VERTICES.value) check_polygon_is_valid(vertices)
[docs] def check_polygon_is_valid(vertices: np.ndarray): """Check if the polygon has no degenerate corners and it is convex. Parameters ---------- vertices : np.ndarray The polygon vertices, in cartesian coordinates Raises ------ ValueError exception if the polygon is invalid. """ vertices_xyz = hp.ang2vec(*vertices.T) # Compute the normal between each pair of neighboring vertices second_vertices = np.roll(vertices_xyz, -1, axis=0) normals = np.cross(vertices_xyz, second_vertices) # Compute the dot products between each normal and a third neighboring vertex. # 'ij,ij->i' means we will multiply each normal vector with the corresponding # vector of the third vertex ('ij,ij': hence element-wise) and sum over each # column for each row '->i'. third_vertices = np.roll(second_vertices, -1, axis=0) dot_products = np.einsum("ij,ij->i", normals, third_vertices) if np.any(np.isclose(dot_products, 0)): raise ValueError(ValidatorsErrors.DEGENERATE_POLYGON.value) if not (np.all(dot_products > 0) or np.all(dot_products < 0)): raise ValueError(ValidatorsErrors.INVALID_CONCAVE_SHAPE.value)
[docs] def validate_box(ra: tuple[float, float], dec: tuple[float, float]): """Checks if ra and dec values are valid for the box search. - Both ranges for ra or dec must have been provided. - Ranges must be defined by a pair of values, in degrees. - Declination values must be unique, provided in ascending order, and belong to the [-90,90] degree range. Parameters ---------- ra : tuple[float, float] Right ascension range, in degrees dec : tuple[float, float] Declination range, in degrees Raises ------ ValueError exception if the box is invalid. """ invalid_range = False if ra is None or len(ra) != 2: invalid_range = True elif dec is None or len(dec) != 2 or dec[0] >= dec[1]: invalid_range = True if invalid_range: raise ValueError(ValidatorsErrors.INVALID_RADEC_RANGE.value) validate_declination_values(dec)