diff --git a/uxarray/grid/arcs.py b/uxarray/grid/arcs.py index 4b8e13d30..33b348527 100644 --- a/uxarray/grid/arcs.py +++ b/uxarray/grid/arcs.py @@ -70,6 +70,8 @@ def point_within_gca(pt_xyz, gca_a_xyz, gca_b_xyz): raise ValueError( "The input Great Circle Arc spans exactly 180 degrees, which can correspond to multiple planes. " "Consider breaking the Great Circle Arc into two smaller arcs." + f"\npoint_within_gca(pt_xyz, gca_a_xyz, gca_b_xyz) got gca_a_xyz={gca_a_xyz}, gca_b_xyz={gca_b_xyz}, " + f"which are 180 degrees apart. (Was checking pt_xyz={pt_xyz}.)" ) # 2. Verify if the point lies on the plane of the GCA @@ -224,7 +226,8 @@ def extreme_gca_latitude(gca_cart, gca_lonlat, extreme_type): """ # Validate extreme_type if (extreme_type != "max") and (extreme_type != "min"): - raise ValueError("extreme_type must be either 'max' or 'min'") + raise ValueError("Invalid extreme_type. Expected 'max' or 'min'.") + # (numba complains about f-strings, so don't put `extreme_type` value in message.) # Extract the two points n1 = gca_cart[0] @@ -305,7 +308,8 @@ def extreme_gca_z(gca_cart, extreme_type): # Validate extreme_type if (extreme_type != "max") and (extreme_type != "min"): - raise ValueError("extreme_type must be either 'max' or 'min'") + raise ValueError("Invalid extreme_type. Expected 'max' or 'min'.") + # (numba complains about f-strings, so don't put `extreme_type` value in message.) # Extract the two points n1 = gca_cart[0] diff --git a/uxarray/grid/area.py b/uxarray/grid/area.py index 318c1d5ca..db1ac6a6b 100644 --- a/uxarray/grid/area.py +++ b/uxarray/grid/area.py @@ -53,7 +53,9 @@ def calculate_face_area( dG, dW = get_tri_quadrature_dg(order) is_gaussian = False else: - raise ValueError("Invalid quadrature rule, specify gaussian or triangular") + raise ValueError( + "Invalid quadrature_rule. Expected 'triangular' or 'gaussian'." + ) # (numba complains about f-strings, so don't put actual value in message.) return _face_area_from_quadrature( x, y, z, dG, dW, is_gaussian, latitude_adjusted_area @@ -253,7 +255,9 @@ def _get_all_face_area_from_coords( dG, dW = get_tri_quadrature_dg(order) is_gaussian = False else: - raise ValueError("Invalid quadrature rule, specify gaussian or triangular") + raise ValueError( + "Invalid quadrature_rule. Expected 'triangular' or 'gaussian'." + ) # (numba complains about f-strings, so don't put actual value in message.) # set initial area of each face to 0 area = np.zeros(n_face) diff --git a/uxarray/grid/bounds.py b/uxarray/grid/bounds.py index 573b6b095..ada893deb 100644 --- a/uxarray/grid/bounds.py +++ b/uxarray/grid/bounds.py @@ -416,7 +416,7 @@ def insert_pt_in_latlonbox(old_box, new_pt, is_lon_periodic=True): else: # Validate longitude point if not np.isnan(lon_pt) and (lon_pt < 0.0 or lon_pt > 2.0 * np.pi): - raise ValueError("Longitude point out of range") + raise ValueError(f"Longitude point out of range (<0 or >2*pi): {lon_pt}") # Check for pole points is_pole_point = False @@ -466,7 +466,7 @@ def insert_pt_in_latlonbox(old_box, new_pt, is_lon_periodic=True): # Ensure widths are non-negative if (d_width_a < 0.0) or (d_width_b < 0.0): raise AssertionError( - "Logic error in longitude box width calculation" + "Logic error in longitude box width calculation: computed negative width" ) # Choose the box with the smaller width @@ -490,8 +490,8 @@ def insert_pt_in_latlonbox(old_box, new_pt, is_lon_periodic=True): # Ensure widths are non-negative if (d_width_a < 0.0) or (d_width_b < 0.0): - raise Exception( - "Logic error in longitude box width calculation" + raise AssertionError( + "Logic error in longitude box width calculation: computed negative width" ) # Choose the box with the smaller width diff --git a/uxarray/grid/coordinates.py b/uxarray/grid/coordinates.py index 5e37128d5..90abdb6af 100644 --- a/uxarray/grid/coordinates.py +++ b/uxarray/grid/coordinates.py @@ -773,7 +773,8 @@ def prepare_points(points, normalize): x, y, z = _normalize_xyz(x, y, z) else: raise DimensionError( - "Points must be a sequence of length 2 (longitude, latitude) or 3 (x, y, z coordinates)." + "Expected len(points) == 2 (for longitude, latitude) or 3 (for x, y, z coordinates); " + f"got len(points)={len(points)}, in grid.coordinates.prepare_points" ) return np.vstack([x, y, z]).T @@ -811,7 +812,8 @@ def points_atleast_2d_xyz(points): points_xyz = points else: raise DimensionError( - "Points are neither Cartesian (shape N x 3) nor Spherical (shape N x 2)." + "Expected points.shape == (N,2) or (N,3) for (lon, lat) or (x, y, z), " + f"respectively; got points.shape == {points.shape}." ) return points_xyz diff --git a/uxarray/grid/geometry.py b/uxarray/grid/geometry.py index f1843096f..165129e95 100644 --- a/uxarray/grid/geometry.py +++ b/uxarray/grid/geometry.py @@ -447,8 +447,8 @@ def _grid_to_matplotlib_polycollection( # Handle unsupported configuration: splitting periodic elements with projection if periodic_elements == "split" and projection is not None: raise ValueError( - "Explicitly projecting lines is not supported. Please pass in your projection " - "using the 'transform' parameter" + 'Must provide `projection` when periodic_elements=="split" ' + "while attempting to create polycollection, but got projection=None." ) # Correct the central longitude and build polygon shells @@ -717,6 +717,7 @@ def pole_point_inside_polygon(pole, face_edges_xyz, face_edges_lonlat): if pole != 1 and pole != -1: raise ValueError("Pole must be 1 (North) or -1 (South)") + # (numba complains about f-strings, so don't put `pole` value in message.) # Define constants within the function pole_point_xyz = np.empty(3, dtype=np.float64) @@ -838,7 +839,11 @@ def pole_point_inside_polygon(pole, face_edges_xyz, face_edges_lonlat): return ((north_intersections + south_intersections) % 2) != 0 else: - raise ValueError("Invalid pole point query.") + # (location will always be 1, -1, or 0 from _classify_polygon_location, + # so it should always be handled by cases above.) + raise AssertionError( + "Internal coding/implementation error: invalid `location`." + ) @njit(cache=True) @@ -1272,8 +1277,10 @@ def barycentric_coordinates_cartesian(polygon_xyz, point_xyz): return weights, nodes - # If the point doesn't reside in the polygon, raise an error - raise ValueError("Point does not reside in polygon") + raise ValueError( + "Point does not reside in polygon, during " + "barycentric_coordinates_cartesian(polygon_xyz, point_xyz)" + ) # (can't do str(float) in numba --> can't include numbers here.) @njit(cache=True) diff --git a/uxarray/grid/grid.py b/uxarray/grid/grid.py index 40ca7710f..edd99239d 100644 --- a/uxarray/grid/grid.py +++ b/uxarray/grid/grid.py @@ -17,7 +17,7 @@ # Import the utility function for opening datasets with fallback from uxarray.core.utils import _open_dataset_with_fallback from uxarray.cross_sections import GridCrossSectionAccessor -from uxarray.errors import DataCenteringError, DimensionError, GridInvalidError +from uxarray.errors import DimensionError, GridInvalidError from uxarray.formatting_html import grid_repr from uxarray.grid.angles import _compute_face_node_angles_convex from uxarray.grid.area import _get_all_face_area_from_coords @@ -172,18 +172,9 @@ def __init__( if not _validate_minimum_ugrid(grid_ds): raise GridInvalidError( "Grid unable to be represented in the UGRID conventions. Representing an unstructured grid requires " - "at least the following variables: ['node_lon'," - "'node_lat', and 'face_node_connectivity']" + "at least the following variables: ['node_lon', 'node_lat', 'face_node_connectivity']," + f"\nbut got grid_ds with data_vars: {list(grid_ds.data_vars)}" ) - - # grid spec not provided, check if grid_ds is a minimum representable UGRID dataset - if source_grid_spec is None: - warnings.warn( - "Attempting to construct a Grid without passing in source_grid_spec. Direct use of Grid constructor" - "is only advised if grid_ds is following the internal unstructured grid definition, including" - "variable and dimension names. Using ux.open_grid() or ux.from_dataset() is suggested.", - Warning, - ) # TODO: more checks for validate grid (lat/lon coords, etc) # mapping of ugrid dimensions and variables to source dataset's conventions @@ -303,10 +294,14 @@ def from_dataset(cls, dataset, use_dual: bool | None = False, **kwargs): grid_ds, source_dims_dict = _read_fesom2_netcdf(dataset) elif source_grid_spec == "Shapefile": raise ValueError( - "Use ux.Grid.from_geodataframe( None: If radius is not positive. """ if radius <= 0: - raise ValueError(f"Sphere radius must be positive, got {radius}") + raise ValueError( + f"{type(self).__name__}.sphere_radius must be positive; cannot set it to {radius}" + ) self._ds.attrs["sphere_radius"] = radius @@ -2379,7 +2410,8 @@ def to_xarray(self, grid_format: str | None = "ugrid"): else: raise ValueError( - f"Invalid grid_format encountered. Expected one of ['ugrid', 'exodus', 'scrip', 'esmf'] but received: {grid_format}" + "Invalid grid_format. Expected one of ['ugrid', 'exodus', 'scrip', 'esmf'], " + f"but got {grid_format!r}, in {type(self).__name__}.to_xarray()" ) return out_ds @@ -2441,7 +2473,8 @@ def to_geodataframe( if engine not in ["spatialpandas", "geopandas"]: raise ValueError( - f"Invalid engine. Expected one of ['spatialpandas', 'geopandas'] but received {engine}" + "Invalid engine. Expected one of ['spatialpandas', 'geopandas'], " + f"but got {engine!r}, in {type(self).__name__}.to_geodataframe()" ) # if project is false, projection is only used for determining central coordinates @@ -2451,7 +2484,8 @@ def to_geodataframe( if periodic_elements == "split": raise ValueError( "Setting ``periodic_elements='split'`` is not supported when a " - "projection is provided." + f"projection is provided; got projection={projection!r} " + f"in {type(self).__name__}.to_geodataframe()." ) if exclude_antimeridian is not None: @@ -2469,7 +2503,8 @@ def to_geodataframe( if periodic_elements not in ["ignore", "exclude", "split"]: raise ValueError( - f"Invalid value for 'periodic_elements'. Expected one of ['exclude', 'split', 'ignore'] but received: {periodic_elements}" + "Invalid periodic_elements. Expected one of ['exclude', 'split', 'ignore'], " + f"but got {periodic_elements!r}, in {type(self).__name__}.to_geodataframe()" ) if self._gdf_cached_parameters["gdf"] is not None: @@ -2547,7 +2582,8 @@ def to_polycollection( if periodic_elements not in ["ignore", "exclude", "split"]: raise ValueError( - f"Invalid value for 'periodic_elements'. Expected one of ['include', 'exclude', 'split'] but received: {periodic_elements}" + "Invalid periodic_elements. Expected one of ['include', 'exclude', 'split'], " + f"but got {periodic_elements!r}, in {type(self).__name__}.to_polycollection()" ) if self._poly_collection_cached_parameters["poly_collection"] is not None: @@ -2627,7 +2663,8 @@ def to_linecollection( """ if periodic_elements not in ["ignore", "exclude", "split"]: raise ValueError( - f"Invalid value for 'periodic_elements'. Expected one of ['ignore', 'exclude', 'split'] but received: {periodic_elements}" + "Invalid periodic_elements. Expected one of ['include', 'exclude', 'split'], " + f"but got {periodic_elements!r}, in {type(self).__name__}.to_linecollection()" ) if self._line_collection_cached_parameters["line_collection"] is not None: @@ -2711,19 +2748,24 @@ def isel(self, inverse_indices: list[str] | set[str] | bool = False, **dim_kwarg from .slice import _slice_edge_indices, _slice_face_indices, _slice_node_indices if len(dim_kwargs) != 1: - raise ValueError("Indexing must be along a single dimension.") + raise ValueError( + f"{type(self).__name__}.isel() expected indexing along a single dimension, " + f"but kwargs imply indexers for: {list(dim_kwargs.keys())}." + ) if "n_node" in dim_kwargs: if inverse_indices: - raise DataCenteringError( - "Inverse indices are not yet supported for node selection, please use face centers" + raise NotImplementedError( + "Grid.isel(n_node=..., inverse_indices=True). " + "Consider selecting along n_face instead, or using inverse_indices=False." ) return _slice_node_indices(self, dim_kwargs["n_node"]) elif "n_edge" in dim_kwargs: if inverse_indices: - raise DataCenteringError( - "Inverse indices are not yet supported for edge selection, please use face centers" + raise NotImplementedError( + "Grid.isel(n_edge=..., inverse_indices=True). " + "Consider selecting along n_face instead, or using inverse_indices=False." ) return _slice_edge_indices(self, dim_kwargs["n_edge"]) @@ -2734,7 +2776,8 @@ def isel(self, inverse_indices: list[str] | set[str] | bool = False, **dim_kwarg else: raise ValueError( # intentionally not DataCenteringError; issue is with kwargs, not data. - "Indexing must be along a grid dimension: ('n_node', 'n_edge', 'n_face')" + "Indexing must be along a grid dimension, one of ['n_node', 'n_edge', 'n_face'], " + f"but provided indexers along: {list(dim_kwargs.keys())}." ) def get_edges_at_constant_latitude(self, lat: float, use_face_bounds: bool = False): @@ -2762,7 +2805,7 @@ def get_edges_at_constant_latitude(self, lat: float, use_face_bounds: bool = Fal if use_face_bounds: raise NotImplementedError( "Computing the intersection using the spherical bounding box" - "is not yet supported." + "(i.e., use_face_bounds=True) is not yet supported." ) else: # Gather per-edge z-coords positionally, mirroring the longitude @@ -2834,7 +2877,7 @@ def get_edges_at_constant_longitude( if use_face_bounds: raise NotImplementedError( "Computing the intersection using the spherical bounding box" - "is not yet supported." + "(i.e. use_face_bounds=True) is not yet supported." ) else: # Positional gather of edge endpoint coords: a concrete connectivity diff --git a/uxarray/grid/integrate.py b/uxarray/grid/integrate.py index b15d9aeef..77028422c 100644 --- a/uxarray/grid/integrate.py +++ b/uxarray/grid/integrate.py @@ -260,6 +260,7 @@ def _get_zonal_face_interval( except ValueError as e: default_print_options = np.get_printoptions() + # TODO: what is build_latlon_box? if str(e) == ( "No intersections are found for the face, please make sure the " "build_latlon_box generates the correct results" @@ -344,7 +345,7 @@ def _process_overlapped_intervals(intervals_df: pl.DataFrame): active_faces.remove(face_idx) else: raise ValueError( - f"Error: Trying to remove face_idx {face_idx} not in active_faces" + f"Cannot end interval for currently-inactive face_idx {face_idx}, at position {position}." ) last_position = position @@ -433,7 +434,9 @@ def _get_faces_constLat_intersection_info( # If the unique intersections numbers is larger than n_edges * 2, then it means the face is concave if len(unique_intersections) > len(valid_edges) * 2: raise ValueError( - "UXarray doesn't support concave face with intersections points as currently, please modify your grids accordingly" + "Concave face found, but not supported by UXarray and would lead to incorrect results " + "during _get_faces_constLat_intersection_info." + f"\nFace edges cartesian coordinates: {face_edges_cart}" ) else: # Now return all the intersections points and the pt_lon_min, pt_lon_max @@ -451,7 +454,8 @@ def _get_faces_constLat_intersection_info( return unique_intersections, pt_lon_min, pt_lon_max elif len(unique_intersections) == 0: raise ValueError( - "No intersections are found for the face, please make sure the build_latlon_box generates the correct results" + "Found 0 intersections for this face, expected at least 1." + f"\nFace edges cartesian coordinates: {face_edges_cart}" ) diff --git a/uxarray/grid/intersections.py b/uxarray/grid/intersections.py index 1e6100854..7ed08f159 100644 --- a/uxarray/grid/intersections.py +++ b/uxarray/grid/intersections.py @@ -394,7 +394,10 @@ def gca_gca_intersection(gca_a_xyz, gca_b_xyz): B208-B232. https://doi.org/10.1137/25M1737614 """ if gca_a_xyz.shape[1] != 3 or gca_b_xyz.shape[1] != 3: - raise DimensionError("The two GCAs must be in the cartesian [x, y, z] format") + raise DimensionError( + "The two GCAs must be in the cartesian [x, y, z] format, " + "but one or both had the wrong shape (expected shape=(2,3))." + ) # (numba doesn't like str(tuple) --> not including inputs' shapes here) w0 = gca_a_xyz[0] w1 = gca_a_xyz[1] diff --git a/uxarray/grid/neighbors.py b/uxarray/grid/neighbors.py index 03a4c7cd5..7328f2442 100644 --- a/uxarray/grid/neighbors.py +++ b/uxarray/grid/neighbors.py @@ -75,8 +75,8 @@ def __init__( self._n_elements = self._source_grid.n_edge else: raise ValueError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers'], " + f"but got {coordinates!r}, in uxarray.KDTree()." ) def _build_from_nodes(self): @@ -106,8 +106,7 @@ def _build_from_nodes(self): else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) self._tree_from_nodes = SKKDTree(coords, metric=self.distance_metric) @@ -141,8 +140,7 @@ def _build_from_face_centers(self): else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) self._tree_from_face_centers = SKKDTree(coords, metric=self.distance_metric) @@ -158,7 +156,10 @@ def _build_from_edge_centers(self): # Sets which values to use for the tree based on the coordinate_system if self.coordinate_system == "cartesian": if self._source_grid.edge_x is None: - raise ValueError("edge_x isn't populated") + raise ValueError( + f"{type(self).__name__}._build_from_edge_centers() when coordinate_system='cartesian' " + "requires edge_x/y/z, but _source_grid.edge_x isn't populated." + ) coords = np.stack( ( @@ -171,7 +172,10 @@ def _build_from_edge_centers(self): elif self.coordinate_system == "spherical": if self._source_grid.edge_lat is None: - raise ValueError("edge_lat isn't populated") + raise ValueError( + f"{type(self).__name__}._build_from_edge_centers() when coordinate_system='spherical' " + "requires edge_lat/lon, but _source_grid.edge_lat isn't populated." + ) coords = np.vstack( ( @@ -182,8 +186,7 @@ def _build_from_edge_centers(self): else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) self._tree_from_edge_centers = SKKDTree(coords, metric=self.distance_metric) @@ -202,8 +205,8 @@ def _current_tree(self): _tree = self._tree_from_edge_centers else: raise ValueError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers']; " + f"got {self._coordinates!r}." ) return _tree @@ -247,8 +250,7 @@ def query( if k < 1 or k > self._n_elements: raise AssertionError( - f"The value of k must be greater than 1 and less than the number of elements used to construct " - f"the tree ({self._n_elements})." + f"Expected 1 <= k <= {self._n_elements} (number of elements used to construct tree); got k={k}" ) if self.coordinate_system == "cartesian": coords = _prepare_xyz_for_query(coords) @@ -258,8 +260,7 @@ def query( ) else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) # perform query with distance @@ -329,9 +330,7 @@ def query_radius( """ if r < 0.0: - raise AssertionError( - "The value of r must be greater than or equal to zero." - ) + raise AssertionError(f"Expected radius r>=0; got r={r}") # Use the correct function to prepare for query based on coordinate type if self.coordinate_system == "cartesian": @@ -342,8 +341,7 @@ def query_radius( ) else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) if count_only: @@ -404,8 +402,8 @@ def coordinates(self, value): self._n_elements = self._source_grid.n_edge else: raise ValueError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers']; " + f"got {self._coordinates!r}." ) @@ -467,8 +465,8 @@ def __init__( self._n_elements = self._source_grid.n_edge else: raise ValueError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers']; " + f"got {self._coordinates!r}." ) def _build_from_face_centers(self): @@ -497,8 +495,7 @@ def _build_from_face_centers(self): ) else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) self._tree_from_face_centers = SKBallTree( @@ -544,7 +541,10 @@ def _build_from_edge_centers(self): # Sets which values to use for the tree based on the coordinate_system if self.coordinate_system == "spherical": if self._source_grid.edge_lat is None: - raise ValueError("edge_lat isn't populated") + raise ValueError( + f"{type(self).__name__}._build_from_edge_centers() when coordinate_system='spherical' " + "requires edge_lat/lon, but _source_grid.edge_lat isn't populated." + ) coords = np.vstack( ( @@ -555,7 +555,10 @@ def _build_from_edge_centers(self): elif self.coordinate_system == "cartesian": if self._source_grid.edge_x is None: - raise ValueError("edge_x isn't populated") + raise ValueError( + f"{type(self).__name__}._build_from_edge_centers() when coordinate_system='cartesian' " + "requires edge_x/y/z, but _source_grid.edge_x isn't populated." + ) coords = np.stack( ( @@ -567,8 +570,7 @@ def _build_from_edge_centers(self): ) else: raise ValueError( - f"Unknown coordinate_system, {self.coordinate_system}, use either 'cartesian' or " - f"'spherical'" + f"Invalid coordinate_system. Expected 'cartesian' or 'spherical', got {self.coordinate_system!r}" ) self._tree_from_edge_centers = SKBallTree( @@ -588,8 +590,8 @@ def _current_tree(self): _tree = self._tree_from_edge_centers else: raise TypeError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers']; " + f"got {self._coordinates!r}." ) return _tree @@ -633,8 +635,7 @@ def query( if k < 1 or k > self._n_elements: raise AssertionError( - f"The value of k must be greater than 1 and less than the number of elements used to construct " - f"the tree ({self._n_elements})." + f"Expected 1 <= k <= {self._n_elements} (number of elements used to construct tree); got k={k}" ) # Use the correct function to prepare for query based on coordinate type @@ -714,9 +715,7 @@ def query_radius( """ if r < 0.0: - raise AssertionError( - "The value of r must be greater than or equal to zero." - ) + raise AssertionError(f"Expected radius r>=0; got r={r}") # Use the correct function to prepare for query based on coordinate type if self.coordinate_system == "spherical": @@ -786,8 +785,8 @@ def coordinates(self, value): self._n_elements = self._source_grid.n_edge else: raise ValueError( - f"Unknown coordinates location, {self._coordinates}, use either 'nodes', 'face centers', " - f"or 'edge centers'" + "Invalid `coordinates`. Expected one of ['nodes', 'face centers', 'edge centers']; " + f"got {self._coordinates!r}." ) @@ -892,10 +891,11 @@ def _initialize_face_hash_table(self): for j in range(j1[eid], j2[eid] + 1): for i in range(i1[eid], i2[eid] + 1): index_to_face[i + self._nx * j].append(eid) - except IndexError: + except IndexError as err: raise IndexError( - "list index out of range. This may indicate incorrect `edge_node_distances` values." - ) + f"list index out of range during {type(self).__name__}._initialize_face_hash_table(). " + "This may indicate incorrect `edge_node_distances` values." + ) from err return index_to_face @@ -1009,7 +1009,10 @@ def _barycentric_coordinates(nodes, point): def _prepare_xy_for_query(xy, use_radians, distance_metric): """Prepares xy coordinates for query with the sklearn BallTree or - KDTree.""" + KDTree. xy actually represents lat/lon, not cartesian x,y, so the + name might be a bit misleading. xy shape should be (n_pairs, 2), + with second dimension corresponding to (lon, lat). + """ xy = np.asarray(xy) @@ -1018,15 +1021,10 @@ def _prepare_xy_for_query(xy, use_radians, distance_metric): xy = np.expand_dims(xy, axis=0) # expected shape is [n_pairs, 2] - if xy.shape[1] == 3: - raise DimensionError( - "The dimension of each coordinate pair must be two (lon, lat). Did you attempt to query using Cartesian " - "(x, y, z) coordinates?" - ) - if xy.shape[1] != 2: raise DimensionError( - "The dimension of each coordinate pair must be two (lon, lat).)" + f"Expected shape (n_pairs, 2) but got shape {xy.shape}, in _prepare_xy_for_query(). " + "(The 2 corresponds to (lon, lat) coordinates.)" ) # swap x and y if the distance metric used is haversine @@ -1043,7 +1041,9 @@ def _prepare_xy_for_query(xy, use_radians, distance_metric): def _prepare_xyz_for_query(xyz): """Prepares xyz coordinates for query with the sklearn BallTree and - KDTree.""" + KDTree. xyz represents cartesian x,y,z coordinates, and should have + shape (n_pairs, 3), with second dimension corresponding to (x, y, z). + """ xyz = np.asarray(xyz) @@ -1052,15 +1052,10 @@ def _prepare_xyz_for_query(xyz): xyz = np.expand_dims(xyz, axis=0) # expected shape is [n_pairs, 3] - if xyz.shape[1] == 2: - raise DimensionError( - "The dimension of each coordinate pair must be three (x, y, z). Did you attempt to query using latlon " - "(lat, lon) coordinates?" - ) - if xyz.shape[1] != 3: raise DimensionError( - "The dimension of each coordinate pair must be three (x, y, z).)" + f"Expected shape (n_pairs, 3) but got shape {xyz.shape}, in _prepare_xyz_for_query(). " + "(The 3 corresponds to (x, y, z) coordinates.)" ) return xyz @@ -1167,9 +1162,9 @@ def _get_element_coords(grid, data_mapping: str, coordinate_system: str): if data_mapping not in prefix_map: raise ValueError( - f"Invalid data_mapping. Expected 'nodes', 'edge centers', or 'face centers', " - f"but received: {data_mapping}" - ) + "Invalid `data_mapping`. Expected one of ['nodes', 'edge centers', 'face centers']; " + f"got {data_mapping!r}, in _get_element_coords(grid, data_mapping=...)." + ) # (kwarg hint data_mapping=... helps distinguish from UxDataArray.data_mapping property.) prefix = prefix_map[data_mapping] @@ -1186,8 +1181,7 @@ def _get_element_coords(grid, data_mapping: str, coordinate_system: str): else: raise ValueError( - f"Invalid coordinate_system. Expected either 'spherical' or 'cartesian', " - f"but received {coordinate_system}" + f"Invalid coordinate_system. Expected 'spherical' or 'cartesian'; got {coordinate_system!r}" ) @@ -1675,7 +1669,7 @@ def _map(self, reduction: Callable, *args, **kwargs): Subclasses must implement this; it is the only thing they need to. """ - raise NotImplementedError + raise NotImplementedError(f"{type(self).__name__}._map()") def mean(self): """Mean of each neighborhood.""" diff --git a/uxarray/grid/slice.py b/uxarray/grid/slice.py index 2d1144c5e..f4ffb864e 100644 --- a/uxarray/grid/slice.py +++ b/uxarray/grid/slice.py @@ -93,7 +93,7 @@ def _slice_node_indices( """ if inclusive is False: - raise NotImplementedError("Exclusive slicing is not yet supported.") + raise NotImplementedError("inclusive=False slicing is not yet supported.") # faces that saddle nodes given in 'indices' face_indices = np.unique( @@ -125,7 +125,7 @@ def _slice_edge_indices( """ if inclusive is False: - raise NotImplementedError("Exclusive slicing is not yet supported.") + raise NotImplementedError("inclusive=False slicing is not yet supported.") # faces that saddle nodes given in 'indices' face_indices = np.unique( @@ -162,7 +162,7 @@ def _slice_face_indices( from uxarray.grid import Grid if inclusive is False: - raise ValueError("Exclusive slicing is not yet supported.") + raise NotImplementedError("inclusive=False slicing is not yet supported.") ds = grid._ds face_indices = np.atleast_1d(np.asarray(indices, dtype=INT_DTYPE)) @@ -244,13 +244,17 @@ def _slice_face_indices( if isinstance(inverse_indices, bool): inverse_indices_ds["face"] = face_indices else: + # TODO: inverse_indices[0] doesn't make sense for list/set of str; + # should probably just be "for index_type in inverse_indices". for index_type in inverse_indices[0]: if index_type in index_types: inverse_indices_ds[index_type] = index_types[index_type] else: raise ValueError( - "Incorrect type of index for `inverse_indices`. Try passing one of the following " - "instead: 'face', 'edge', 'node'" + f"Invalid value in inverse_indices: {index_type!r}. " + "Expected inverse_indices=True/False, or iterable of str " + "including only values from ['face', 'edge', 'node'], " + f"but got inverse_indices={inverse_indices}." ) return Grid.from_dataset( diff --git a/uxarray/grid/utils.py b/uxarray/grid/utils.py index a33f50629..6807a9c18 100644 --- a/uxarray/grid/utils.py +++ b/uxarray/grid/utils.py @@ -499,7 +499,10 @@ def make_setter(key: str): def setter(self, value): if not isinstance(value, xr.DataArray): - raise TypeError(f"{key} must be an xr.DataArray") + raise TypeError( + f"Expected xr.DataArray value when setting Grid.{key}=value; " + f"got type(value)={type(value)}." + ) self._ds[key] = value return setter