relu-q15 0.1.0

ReLU en virgule fixe Q15 pour systèmes embarqués `no_std`, zéro dépendance
Documentation
// Copyright (C) 2026 Jorge Andre Castro
//
// Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier
// selon les termes de la Licence Publique Générale GNU telle que publiée par la
// Free Software Foundation, soit la version 2 de la licence, soit (à votre convention)
// n'importe quelle version ultérieure.

//! # relu-q15
//!
//! Fonction d'activation ReLU en virgule fixe Q15 pour systèmes embarqués.
//!
//! ## Caractéristiques
//!
//! - `#![no_std]` aucune dépendance à la bibliothèque standard
//! - Arithmétique entière pure (pas de flottants, pas de `libm`)
//! - Compatible RP2040 (Cortex-M0+)  sans fpu, (sans unité de calcul en virgule flottante)
//! - Temps d'exécution **constant** (déterministe) idéal pour les noyaux temps réel
//! - Zéro allocation dynamique, traitement in-place possible
//!
//! ## Format Q15
//!
//! En Q15, un `i16` représente un nombre réel dans `[-1.0, 1.0[` :
//! ```text
//! valeur_réelle = valeur_i16 / 32768.0
//! ```
//! Exemples :
//! - `0`      → 0.0
//! - `16384`  → 0.5
//! - `32767`  → ≈ 1.0 (i16::MAX)
//! - `-32768` → -1.0 (i16::MIN)
//!
//! ## Algorithme
//!
//! ReLU (Rectified Linear Unit) : `f(x) = max(0, x)`
//!
//! - Si `x < 0`  → retourne `0`
//! - Si `x >= 0` → retourne `x` inchangé (identité)
//!
//! Cas limites traités explicitement :
//! - `i16::MIN` (-32768) → `0`  (valeur la plus négative possible en Q15)
//! - `i16::MAX` (32767)  → `32767` (passé tel quel)
//! - `0`                 → `0`  (zéro reste zéro)
//!
//! ## Exemple
//!
//! ```rust
//! use relu_q15::{relu_q15, relu_slice_q15};
//!
//! // Valeurs négatives → 0
//! assert_eq!(relu_q15(-32768), 0); // i16::MIN
//! assert_eq!(relu_q15(-16384), 0); // -0.5
//! assert_eq!(relu_q15(-1),     0);
//!
//! // Zéro → 0
//! assert_eq!(relu_q15(0), 0);
//!
//! // Valeurs positives → identité
//! assert_eq!(relu_q15(1),      1);
//! assert_eq!(relu_q15(16384),  16384); // 0.5
//! assert_eq!(relu_q15(32767),  32767); // i16::MAX
//!
//! // Traitement in-place d'un vecteur
//! let mut buf = [-32768i16, -100, 0, 100, 32767];
//! relu_slice_q15(&mut buf);
//! assert_eq!(buf, [0, 0, 0, 100, 32767]);
//! ```

#![no_std]
#![forbid(unsafe_code)]

/// Calcule la fonction d'activation ReLU pour un seul nombre en virgule fixe Q15.
///
/// `f(x) = max(0, x)`
///
/// # Cas limites
///
/// | Entrée         | Valeur Q15 | Sortie |
/// |----------------|-----------|--------|
/// | `i16::MIN`     | -32768    | `0`    |
/// | `-1`           | -1        | `0`    |
/// | `0`            | 0         | `0`    |
/// | `1`            | 1         | `1`    |
/// | `i16::MAX`     | 32767     | `32767`|
///
/// # Exemple
///
/// ```rust
/// use relu_q15::relu_q15;
/// assert_eq!(relu_q15(-16384), 0);     // exp(-0.5) → tronqué
/// assert_eq!(relu_q15(0),      0);
/// assert_eq!(relu_q15(16384),  16384); // 0.5 → identité
/// ```
#[inline]
pub fn relu_q15(x: i16) -> i16 {
    // Branchement sur le bit de signe : aucune opération arithmétique nécessaire.
    // Équivalent à `max(0, x)` sans risque de débordement car on ne fait
    // jamais de multiplication ni d'addition.
    if x < 0 { 0 } else { x }
}

/// Applique ReLU sur un slice de données en virgule fixe Q15, **in-place**.
///
/// Chaque élément `v` est remplacé par `max(0, v)`.
/// L'opération in-place évite toute allocation dynamique, ce qui est
/// critique sur des MCU avec quelques Ko de RAM (ex. RP2040 : 264 Ko).
///
/// # Slice vide
///
/// Un slice vide (`&mut []`) est accepté sans erreur : la fonction retourne
/// immédiatement sans rien faire.
///
/// # Exemple
///
/// ```rust
/// use relu_q15::relu_slice_q15;
///
/// let mut data = [-32768i16, -1, 0, 1, 32767];
/// relu_slice_q15(&mut data);
/// assert_eq!(data, [0, 0, 0, 1, 32767]);
///
/// // Slice vide : aucun effet, aucune panique
/// let mut empty: [i16; 0] = [];
/// relu_slice_q15(&mut empty);
/// ```
#[inline]
pub fn relu_slice_q15(data: &mut [i16]) {
    for val in data.iter_mut() {
        *val = relu_q15(*val);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    //  relu_q15 scalaire 

    #[test]
    fn test_relu_min_is_zero() {
        // i16::MIN (-32768) est la valeur la plus négative : doit retourner 0
        assert_eq!(relu_q15(i16::MIN), 0);
    }

    #[test]
    fn test_relu_minus_one_is_zero() {
        assert_eq!(relu_q15(-1), 0);
    }

    #[test]
    fn test_relu_zero_is_zero() {
        // Zéro n'est pas négatif : relu(0) = 0
        assert_eq!(relu_q15(0), 0);
    }

    #[test]
    fn test_relu_one_is_one() {
        assert_eq!(relu_q15(1), 1);
    }

    #[test]
    fn test_relu_max_is_identity() {
        // i16::MAX (32767) doit être retourné tel quel
        assert_eq!(relu_q15(i16::MAX), i16::MAX);
    }

    #[test]
    fn test_relu_negative_half() {
        // -0.5 en Q15 → -16384 → 0
        assert_eq!(relu_q15(-16384), 0);
    }

    #[test]
    fn test_relu_positive_half() {
        // 0.5 en Q15 → 16384 → identité
        assert_eq!(relu_q15(16384), 16384);
    }

    #[test]
    fn test_relu_negative_quarter() {
        // -0.25 en Q15 → -8192 → 0
        assert_eq!(relu_q15(-8192), 0);
    }

    #[test]
    fn test_relu_positive_quarter() {
        // 0.25 en Q15 → 8192 → identité
        assert_eq!(relu_q15(8192), 8192);
    }

    #[test]
    fn test_relu_idempotent_positive() {
        // relu(relu(x)) == relu(x) pour tout x positif
        for x in [1i16, 100, 8192, 16384, 32767] {
            assert_eq!(relu_q15(relu_q15(x)), relu_q15(x));
        }
    }

    #[test]
    fn test_relu_idempotent_zero() {
        assert_eq!(relu_q15(relu_q15(0)), relu_q15(0));
    }

    #[test]
    fn test_relu_output_never_negative() {
        // relu(x) >= 0 pour tout x possible en i16
        // On teste un sous-ensemble représentatif
        for x in [i16::MIN, -16384, -8192, -1, 0, 1, 8192, 16384, i16::MAX] {
            assert!(relu_q15(x) >= 0, "relu_q15({}) doit être >= 0", x);
        }
    }

    #[test]
    fn test_relu_output_never_exceeds_input_positive() {
        // Pour x >= 0, relu(x) == x (identité stricte)
        for x in [0i16, 1, 8192, 16384, 32767] {
            assert_eq!(relu_q15(x), x, "relu_q15({}) doit être == {}", x, x);
        }
    }

    //  relu_slice_q15 

    #[test]
    fn test_slice_empty() {
        // Slice vide : aucune panique, aucun effet
        let mut empty: [i16; 0] = [];
        relu_slice_q15(&mut empty); // ne doit pas paniquer
    }

    #[test]
    fn test_slice_single_negative() {
        let mut data = [-1i16];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0]);
    }

    #[test]
    fn test_slice_single_zero() {
        let mut data = [0i16];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0]);
    }

    #[test]
    fn test_slice_single_positive() {
        let mut data = [16384i16];
        relu_slice_q15(&mut data);
        assert_eq!(data, [16384]);
    }

    #[test]
    fn test_slice_mixed() {
        let mut data = [-32768i16, -1, 0, 1, 32767];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0, 0, 0, 1, 32767]);
    }

    #[test]
    fn test_slice_all_negative() {
        let mut data = [-32768i16, -16384, -8192, -1];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0, 0, 0, 0]);
    }

    #[test]
    fn test_slice_all_positive() {
        let mut data = [1i16, 8192, 16384, 32767];
        relu_slice_q15(&mut data);
        assert_eq!(data, [1, 8192, 16384, 32767]);
    }

    #[test]
    fn test_slice_all_zero() {
        let mut data = [0i16; 8];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0i16; 8]);
    }

    #[test]
    fn test_slice_idempotent() {
        // Appliquer relu deux fois doit donner le même résultat
        let mut data1 = [-32768i16, -100, 0, 100, 32767];
        let mut data2 = data1;
        relu_slice_q15(&mut data1);
        relu_slice_q15(&mut data1); // deuxième passage
        relu_slice_q15(&mut data2); // un seul passage
        assert_eq!(data1, data2);
    }

    #[test]
    fn test_slice_boundary_values() {
        // i16::MIN et i16::MAX dans le même buffer
        let mut data = [i16::MIN, i16::MAX];
        relu_slice_q15(&mut data);
        assert_eq!(data, [0, i16::MAX]);
    }
}