From a5dd5cb4531d5c5e6f9273e2ac46ca4d4bab5b67 Mon Sep 17 00:00:00 2001 From: shaun0927 Date: Fri, 17 Apr 2026 13:08:35 +0900 Subject: [PATCH 1/2] fix: validate inputs and preserve numpy return type in calc_quantile_loss Two related consistency fixes for pypots/nn/functional/error.py: 1. calc_quantile_loss was the only calc_* function that did not call _check_inputs(). NaN or shape-mismatched inputs therefore produced silently wrong numeric results instead of the clear AssertionError that calc_mae/calc_mse/calc_rmse/calc_mre raise. This adds the same guard so all error metrics share a single validation contract. 2. After numpy support was introduced in #822, numpy inputs were always returned as a torch.Tensor, breaking the existing Union[float, torch.Tensor] contract that sibling metrics honor (numpy in -> numpy out). The function now converts back to a numpy scalar when the caller passed numpy arrays. Verified with the sibling metrics on both numpy and torch paths. --- pypots/nn/functional/error.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pypots/nn/functional/error.py b/pypots/nn/functional/error.py index e194f24c..76c987fc 100644 --- a/pypots/nn/functional/error.py +++ b/pypots/nn/functional/error.py @@ -260,6 +260,12 @@ def calc_quantile_loss( q: float, eval_points: Union[np.ndarray, torch.Tensor], ) -> Union[float, torch.Tensor]: + # check shapes and values of inputs, consistent with sibling calc_* functions + _check_inputs(predictions, targets, eval_points) + + # preserve numpy-in/numpy-out contract used by calc_mae/calc_mse/calc_rmse/calc_mre + numpy_in = isinstance(predictions, np.ndarray) + # Handle numpy arrays by converting to torch tensors if isinstance(predictions, np.ndarray): predictions = torch.from_numpy(predictions) @@ -271,6 +277,8 @@ def calc_quantile_loss( quantile_loss = 2 * torch.sum( torch.abs((predictions - targets) * eval_points * ((targets <= predictions) * 1.0 - q)) ) + if numpy_in: + return quantile_loss.detach().cpu().numpy() return quantile_loss From fafdf03e3c3d84144e8d41bd3d9c9950a9b89b8f Mon Sep 17 00:00:00 2001 From: shaun0927 Date: Fri, 17 Apr 2026 14:14:29 +0900 Subject: [PATCH 2/2] fix: skip shape check in calc_quantile_loss to keep calc_quantile_crps_sum working The initial fix passed _check_inputs with default check_shape=True, which rejects the intentional broadcasting in the calc_quantile_crps_sum code path (q_pred has shape (B,) while targets has shape (B, T)). Passing check_shape=False keeps the NaN/type guards that motivated this change while allowing both internal callers (calc_quantile_crps and calc_quantile_crps_sum) to keep broadcasting as they did before. _check_inputs still validates mask.shape == targets.shape, so the mask contract is unchanged. --- pypots/nn/functional/error.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pypots/nn/functional/error.py b/pypots/nn/functional/error.py index 76c987fc..10d3af1c 100644 --- a/pypots/nn/functional/error.py +++ b/pypots/nn/functional/error.py @@ -260,8 +260,10 @@ def calc_quantile_loss( q: float, eval_points: Union[np.ndarray, torch.Tensor], ) -> Union[float, torch.Tensor]: - # check shapes and values of inputs, consistent with sibling calc_* functions - _check_inputs(predictions, targets, eval_points) + # check types and NaN (but not predictions/targets shape, which is + # broadcast here and explicitly differs in the calc_quantile_crps_sum + # caller). Mask shape is still validated against targets by _check_inputs. + _check_inputs(predictions, targets, eval_points, check_shape=False) # preserve numpy-in/numpy-out contract used by calc_mae/calc_mse/calc_rmse/calc_mre numpy_in = isinstance(predictions, np.ndarray)