Skip to main content

relu_q15/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2//
3// Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier
4// selon les termes de la Licence Publique Générale GNU telle que publiée par la
5// Free Software Foundation, soit la version 2 de la licence, soit (à votre convention)
6// n'importe quelle version ultérieure.
7
8//! # relu-q15
9//!
10//! Fonction d'activation ReLU en virgule fixe Q15 pour systèmes embarqués.
11//!
12//! ## Caractéristiques
13//!
14//! - `#![no_std]` aucune dépendance à la bibliothèque standard
15//! - Arithmétique entière pure (pas de flottants, pas de `libm`)
16//! - Compatible RP2040 (Cortex-M0+)  sans fpu, (sans unité de calcul en virgule flottante)
17//! - Temps d'exécution **constant** (déterministe) idéal pour les noyaux temps réel
18//! - Zéro allocation dynamique, traitement in-place possible
19//!
20//! ## Format Q15
21//!
22//! En Q15, un `i16` représente un nombre réel dans `[-1.0, 1.0[` :
23//! ```text
24//! valeur_réelle = valeur_i16 / 32768.0
25//! ```
26//! Exemples :
27//! - `0`      → 0.0
28//! - `16384`  → 0.5
29//! - `32767`  → ≈ 1.0 (i16::MAX)
30//! - `-32768` → -1.0 (i16::MIN)
31//!
32//! ## Algorithme
33//!
34//! ReLU (Rectified Linear Unit) : `f(x) = max(0, x)`
35//!
36//! - Si `x < 0`  → retourne `0`
37//! - Si `x >= 0` → retourne `x` inchangé (identité)
38//!
39//! Cas limites traités explicitement :
40//! - `i16::MIN` (-32768) → `0`  (valeur la plus négative possible en Q15)
41//! - `i16::MAX` (32767)  → `32767` (passé tel quel)
42//! - `0`                 → `0`  (zéro reste zéro)
43//!
44//! ## Exemple
45//!
46//! ```rust
47//! use relu_q15::{relu_q15, relu_slice_q15};
48//!
49//! // Valeurs négatives → 0
50//! assert_eq!(relu_q15(-32768), 0); // i16::MIN
51//! assert_eq!(relu_q15(-16384), 0); // -0.5
52//! assert_eq!(relu_q15(-1),     0);
53//!
54//! // Zéro → 0
55//! assert_eq!(relu_q15(0), 0);
56//!
57//! // Valeurs positives → identité
58//! assert_eq!(relu_q15(1),      1);
59//! assert_eq!(relu_q15(16384),  16384); // 0.5
60//! assert_eq!(relu_q15(32767),  32767); // i16::MAX
61//!
62//! // Traitement in-place d'un vecteur
63//! let mut buf = [-32768i16, -100, 0, 100, 32767];
64//! relu_slice_q15(&mut buf);
65//! assert_eq!(buf, [0, 0, 0, 100, 32767]);
66//! ```
67
68#![no_std]
69#![forbid(unsafe_code)]
70
71/// Calcule la fonction d'activation ReLU pour un seul nombre en virgule fixe Q15.
72///
73/// `f(x) = max(0, x)`
74///
75/// # Cas limites
76///
77/// | Entrée         | Valeur Q15 | Sortie |
78/// |----------------|-----------|--------|
79/// | `i16::MIN`     | -32768    | `0`    |
80/// | `-1`           | -1        | `0`    |
81/// | `0`            | 0         | `0`    |
82/// | `1`            | 1         | `1`    |
83/// | `i16::MAX`     | 32767     | `32767`|
84///
85/// # Exemple
86///
87/// ```rust
88/// use relu_q15::relu_q15;
89/// assert_eq!(relu_q15(-16384), 0);     // exp(-0.5) → tronqué
90/// assert_eq!(relu_q15(0),      0);
91/// assert_eq!(relu_q15(16384),  16384); // 0.5 → identité
92/// ```
93#[inline]
94pub fn relu_q15(x: i16) -> i16 {
95    // Branchement sur le bit de signe : aucune opération arithmétique nécessaire.
96    // Équivalent à `max(0, x)` sans risque de débordement car on ne fait
97    // jamais de multiplication ni d'addition.
98    if x < 0 { 0 } else { x }
99}
100
101/// Applique ReLU sur un slice de données en virgule fixe Q15, **in-place**.
102///
103/// Chaque élément `v` est remplacé par `max(0, v)`.
104/// L'opération in-place évite toute allocation dynamique, ce qui est
105/// critique sur des MCU avec quelques Ko de RAM (ex. RP2040 : 264 Ko).
106///
107/// # Slice vide
108///
109/// Un slice vide (`&mut []`) est accepté sans erreur : la fonction retourne
110/// immédiatement sans rien faire.
111///
112/// # Exemple
113///
114/// ```rust
115/// use relu_q15::relu_slice_q15;
116///
117/// let mut data = [-32768i16, -1, 0, 1, 32767];
118/// relu_slice_q15(&mut data);
119/// assert_eq!(data, [0, 0, 0, 1, 32767]);
120///
121/// // Slice vide : aucun effet, aucune panique
122/// let mut empty: [i16; 0] = [];
123/// relu_slice_q15(&mut empty);
124/// ```
125#[inline]
126pub fn relu_slice_q15(data: &mut [i16]) {
127    for val in data.iter_mut() {
128        *val = relu_q15(*val);
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    //  relu_q15 scalaire 
137
138    #[test]
139    fn test_relu_min_is_zero() {
140        // i16::MIN (-32768) est la valeur la plus négative : doit retourner 0
141        assert_eq!(relu_q15(i16::MIN), 0);
142    }
143
144    #[test]
145    fn test_relu_minus_one_is_zero() {
146        assert_eq!(relu_q15(-1), 0);
147    }
148
149    #[test]
150    fn test_relu_zero_is_zero() {
151        // Zéro n'est pas négatif : relu(0) = 0
152        assert_eq!(relu_q15(0), 0);
153    }
154
155    #[test]
156    fn test_relu_one_is_one() {
157        assert_eq!(relu_q15(1), 1);
158    }
159
160    #[test]
161    fn test_relu_max_is_identity() {
162        // i16::MAX (32767) doit être retourné tel quel
163        assert_eq!(relu_q15(i16::MAX), i16::MAX);
164    }
165
166    #[test]
167    fn test_relu_negative_half() {
168        // -0.5 en Q15 → -16384 → 0
169        assert_eq!(relu_q15(-16384), 0);
170    }
171
172    #[test]
173    fn test_relu_positive_half() {
174        // 0.5 en Q15 → 16384 → identité
175        assert_eq!(relu_q15(16384), 16384);
176    }
177
178    #[test]
179    fn test_relu_negative_quarter() {
180        // -0.25 en Q15 → -8192 → 0
181        assert_eq!(relu_q15(-8192), 0);
182    }
183
184    #[test]
185    fn test_relu_positive_quarter() {
186        // 0.25 en Q15 → 8192 → identité
187        assert_eq!(relu_q15(8192), 8192);
188    }
189
190    #[test]
191    fn test_relu_idempotent_positive() {
192        // relu(relu(x)) == relu(x) pour tout x positif
193        for x in [1i16, 100, 8192, 16384, 32767] {
194            assert_eq!(relu_q15(relu_q15(x)), relu_q15(x));
195        }
196    }
197
198    #[test]
199    fn test_relu_idempotent_zero() {
200        assert_eq!(relu_q15(relu_q15(0)), relu_q15(0));
201    }
202
203    #[test]
204    fn test_relu_output_never_negative() {
205        // relu(x) >= 0 pour tout x possible en i16
206        // On teste un sous-ensemble représentatif
207        for x in [i16::MIN, -16384, -8192, -1, 0, 1, 8192, 16384, i16::MAX] {
208            assert!(relu_q15(x) >= 0, "relu_q15({}) doit être >= 0", x);
209        }
210    }
211
212    #[test]
213    fn test_relu_output_never_exceeds_input_positive() {
214        // Pour x >= 0, relu(x) == x (identité stricte)
215        for x in [0i16, 1, 8192, 16384, 32767] {
216            assert_eq!(relu_q15(x), x, "relu_q15({}) doit être == {}", x, x);
217        }
218    }
219
220    //  relu_slice_q15 
221
222    #[test]
223    fn test_slice_empty() {
224        // Slice vide : aucune panique, aucun effet
225        let mut empty: [i16; 0] = [];
226        relu_slice_q15(&mut empty); // ne doit pas paniquer
227    }
228
229    #[test]
230    fn test_slice_single_negative() {
231        let mut data = [-1i16];
232        relu_slice_q15(&mut data);
233        assert_eq!(data, [0]);
234    }
235
236    #[test]
237    fn test_slice_single_zero() {
238        let mut data = [0i16];
239        relu_slice_q15(&mut data);
240        assert_eq!(data, [0]);
241    }
242
243    #[test]
244    fn test_slice_single_positive() {
245        let mut data = [16384i16];
246        relu_slice_q15(&mut data);
247        assert_eq!(data, [16384]);
248    }
249
250    #[test]
251    fn test_slice_mixed() {
252        let mut data = [-32768i16, -1, 0, 1, 32767];
253        relu_slice_q15(&mut data);
254        assert_eq!(data, [0, 0, 0, 1, 32767]);
255    }
256
257    #[test]
258    fn test_slice_all_negative() {
259        let mut data = [-32768i16, -16384, -8192, -1];
260        relu_slice_q15(&mut data);
261        assert_eq!(data, [0, 0, 0, 0]);
262    }
263
264    #[test]
265    fn test_slice_all_positive() {
266        let mut data = [1i16, 8192, 16384, 32767];
267        relu_slice_q15(&mut data);
268        assert_eq!(data, [1, 8192, 16384, 32767]);
269    }
270
271    #[test]
272    fn test_slice_all_zero() {
273        let mut data = [0i16; 8];
274        relu_slice_q15(&mut data);
275        assert_eq!(data, [0i16; 8]);
276    }
277
278    #[test]
279    fn test_slice_idempotent() {
280        // Appliquer relu deux fois doit donner le même résultat
281        let mut data1 = [-32768i16, -100, 0, 100, 32767];
282        let mut data2 = data1;
283        relu_slice_q15(&mut data1);
284        relu_slice_q15(&mut data1); // deuxième passage
285        relu_slice_q15(&mut data2); // un seul passage
286        assert_eq!(data1, data2);
287    }
288
289    #[test]
290    fn test_slice_boundary_values() {
291        // i16::MIN et i16::MAX dans le même buffer
292        let mut data = [i16::MIN, i16::MAX];
293        relu_slice_q15(&mut data);
294        assert_eq!(data, [0, i16::MAX]);
295    }
296}