Skip to main content

rill_ml/loss/
squared.rs

1//! Squared error loss: `0.5 * (y - y_hat)^2`.
2//!
3//! The gradient with respect to the prediction is `(y_hat - y)`.
4
5use crate::error::{RillError, ensure_finite_target};
6
7/// Squared error loss.
8#[derive(Debug, Clone, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct SquaredError;
11
12impl SquaredError {
13    /// Create a new squared error loss.
14    pub const fn new() -> Self {
15        Self
16    }
17
18    /// Compute `0.5 * (prediction - target)^2`.
19    pub fn loss(prediction: f64, target: f64) -> f64 {
20        let diff = prediction - target;
21        0.5 * diff * diff
22    }
23
24    /// Compute the derivative w.r.t. prediction: `prediction - target`.
25    pub fn gradient(prediction: f64, target: f64) -> f64 {
26        prediction - target
27    }
28
29    /// Validate that both prediction and target are finite.
30    pub fn validate(prediction: f64, target: f64) -> Result<(), RillError> {
31        crate::error::ensure_finite("prediction", prediction)?;
32        ensure_finite_target(target)
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn loss_at_zero_residual_is_zero() {
42        assert_eq!(SquaredError::loss(5.0, 5.0), 0.0);
43    }
44
45    #[test]
46    fn loss_value() {
47        assert!((SquaredError::loss(3.0, 1.0) - 2.0).abs() < 1e-12);
48    }
49
50    #[test]
51    fn gradient_sign() {
52        assert!((SquaredError::gradient(3.0, 1.0) - 2.0).abs() < 1e-12);
53        assert!((SquaredError::gradient(1.0, 3.0) + 2.0).abs() < 1e-12);
54    }
55}