Skip to main content

burn_tensor/tensor/linalg/
vector_norm.rs

1use crate::check::unwrap_dim_index;
2use crate::tensor::Tensor;
3use crate::{
4    AsIndex, ElementConversion,
5    kind::{Numeric, Ordered},
6};
7#[allow(unused_imports)]
8use num_traits::float::Float;
9/// Specifies the type of norm to compute.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum Norm {
12    /// L0 norm (count of non-zero elements)
13    L0,
14
15    /// L1 norm (sum of absolute values)
16    L1,
17
18    /// L2 norm (Euclidean norm)
19    L2,
20
21    /// L:INFINITY norm (maximum absolute value)
22    LInf,
23
24    /// L:NEG_INFINITY norm (minimum absolute value)
25    LNegInf,
26
27    /// Lp norm (generalized norm)
28    Lp(f64),
29}
30
31impl Norm {
32    /// Get the exponent of the norm.
33    pub fn to_exponent(self) -> f64 {
34        use Norm::*;
35        match self {
36            L0 => 0.0,
37            L1 => 1.0,
38            L2 => 2.0,
39            LInf => f64::INFINITY,
40            LNegInf => f64::NEG_INFINITY,
41            Lp(p) => p,
42        }
43    }
44}
45
46impl From<u32> for Norm {
47    fn from(value: u32) -> Self {
48        use Norm::*;
49        match value {
50            0 => L0,
51            1 => L1,
52            2 => L2,
53            u32::MAX => LInf,
54            _ => Lp(value as f64),
55        }
56    }
57}
58
59impl From<i32> for Norm {
60    fn from(value: i32) -> Self {
61        use Norm::*;
62        match value {
63            0 => L0,
64            1 => L1,
65            2 => L2,
66            i32::MAX => LInf,
67            i32::MIN => LNegInf,
68            _ => Lp(value as f64),
69        }
70    }
71}
72
73impl From<f32> for Norm {
74    fn from(value: f32) -> Self {
75        use Norm::*;
76        match value {
77            0.0 => L0,
78            1.0 => L1,
79            2.0 => L2,
80            f32::INFINITY => LInf,
81            f32::NEG_INFINITY => LNegInf,
82            _ => Lp(value as f64),
83        }
84    }
85}
86
87impl From<f64> for Norm {
88    fn from(value: f64) -> Self {
89        use Norm::*;
90        match value {
91            0.0 => L0,
92            1.0 => L1,
93            2.0 => L2,
94            f64::INFINITY => LInf,
95            f64::NEG_INFINITY => LNegInf,
96            _ => Lp(value),
97        }
98    }
99}
100
101/// Computes the vector norm of a tensor along a specified dimension.
102///
103/// Generic dispatch wrapper over specialized / optimized norms.
104///
105/// See:
106/// - [torch.linalg.vector_norm](https://pytorch.org/docs/stable/generated/torch.linalg.vector_norm.html)
107/// - [numpy.linalg.vector_norm](https://numpy.org/doc/stable/reference/generated/numpy.linalg.vector_norm.html)
108///
109/// # Arguments
110///
111/// * `x` - The input tensor.
112/// * `norm` - The selected norm.
113/// * `dim` - The dimension to compute the norm over.
114///   Negative dimensions are supported and count from the end.
115///
116/// # Returns
117///
118/// The vector norm of the input tensor.
119pub fn vector_norm<const D: usize>(
120    x: Tensor<D>,
121    norm: impl Into<Norm>,
122    dim: impl AsIndex,
123) -> Tensor<D> {
124    let dim = unwrap_dim_index(dim.try_dim_index(D), "Vector Norm");
125    lp_norm_impl(x, norm.into().to_exponent(), dim)
126}
127
128/// Computes the general ``L(p)`` norm of a tensor along a specified dimension.
129///
130/// Uses the specialized implementations for:
131/// * 0.0
132/// * 1.0
133/// * 2.0
134/// * 2 * N for integral N,
135/// * f64::INFINITY,
136/// * f64::NEG_INFINITY,
137///
138/// # Arguments
139///
140/// * `x` - The input tensor.
141/// * `p` - The exponent of the Lp norm.
142/// * `dim` - The dimension to compute the norm over.
143///   Negative dimensions are supported and count from the end.
144///
145/// # Returns
146///
147/// The ``L(p)`` norm of the input tensor.
148pub fn lp_norm<const D: usize>(x: Tensor<D>, p: f64, dim: impl AsIndex) -> Tensor<D> {
149    let dim = unwrap_dim_index(dim.try_dim_index(D), "Lp Norm");
150    lp_norm_impl(x, p, dim)
151}
152
153fn lp_norm_impl<const D: usize>(x: Tensor<D>, p: f64, dim: usize) -> Tensor<D> {
154    match p {
155        0.0 => l0_norm_impl(x, dim),
156        1.0 => l1_norm_impl(x, dim),
157        2.0 => l2_norm_impl(x, dim),
158        p if is_even_integer(p) => lp_signed_norm(x, p as u32, dim),
159        f64::INFINITY => max_abs_norm_impl(x, dim),
160        f64::NEG_INFINITY => min_abs_norm_impl(x, dim),
161        _ => lp_norm_base(x, p, dim),
162    }
163}
164
165/// Normalize a tensor versus its `vector_norm`.
166///
167/// Equivalent to ``x.clone() / vector_norm(x, norm, dim).clamp_min(eps)``.
168///
169/// # Arguments
170///
171/// * `x` - The input tensor.
172/// * `norm` - The selected norm.
173/// * `dim` - The dimension to compute the norm over.
174///   Negative dimensions are supported and count from the end.
175/// * `eps` - The epsilon for the norm.
176///
177/// # Returns
178///
179/// The normalized tensor.
180pub fn vector_normalize<const D: usize, E: ElementConversion>(
181    x: Tensor<D>,
182    norm: impl Into<Norm>,
183    dim: impl AsIndex,
184    eps: E,
185) -> Tensor<D> {
186    let dim = unwrap_dim_index(dim.try_dim_index(D), "Vector Normalize");
187    let norm = lp_norm_impl(x.clone(), norm.into().to_exponent(), dim).clamp_min(eps);
188    x / norm
189}
190
191/// Computes the L0 norm of a tensor along a specified dimension.
192///
193/// # Arguments
194///
195/// * `x` - The input tensor.
196/// * `dim` - The dimension to compute the norm over.
197///   Negative dimensions are supported and count from the end.
198///
199/// # Returns
200///
201/// The L0 norm of the input tensor.
202pub fn l0_norm<const D: usize, K>(x: Tensor<D, K>, dim: impl AsIndex) -> Tensor<D, K>
203where
204    K: Numeric,
205{
206    let dim = unwrap_dim_index(dim.try_dim_index(D), "L0 Norm");
207    l0_norm_impl(x, dim)
208}
209
210fn l0_norm_impl<const D: usize, K>(x: Tensor<D, K>, dim: usize) -> Tensor<D, K>
211where
212    K: Numeric,
213{
214    x.zeros_like()
215        .mask_fill(x.not_equal_scalar(0), 1)
216        .sum_dim(dim)
217}
218
219/// Computes the L1 norm of a tensor along a specified dimension.
220///
221/// This is a convenience function that wraps `vector_norm` with `p = 1.0`.
222///
223/// # Arguments
224///
225/// * `x` - The input tensor.
226/// * `dim` - The dimension to compute the norm over.
227///   Negative dimensions are supported and count from the end.
228///
229/// # Returns
230///
231/// The L1 norm of the input tensor.
232pub fn l1_norm<const D: usize, K>(x: Tensor<D, K>, dim: impl AsIndex) -> Tensor<D, K>
233where
234    K: Numeric,
235{
236    let dim = unwrap_dim_index(dim.try_dim_index(D), "L1 Norm");
237    l1_norm_impl(x, dim)
238}
239
240fn l1_norm_impl<const D: usize, K>(x: Tensor<D, K>, dim: usize) -> Tensor<D, K>
241where
242    K: Numeric,
243{
244    x.abs().sum_dim(dim)
245}
246
247/// Computes the L2 norm of a tensor along a specified dimension.
248///
249/// # Arguments
250///
251/// * `x` - The input tensor.
252/// * `dim` - The dimension to compute the norm over.
253///   Negative dimensions are supported and count from the end.
254///
255/// # Returns
256///
257/// The L2 norm of the input tensor.
258pub fn l2_norm<const D: usize>(x: Tensor<D>, dim: impl AsIndex) -> Tensor<D> {
259    let dim = unwrap_dim_index(dim.try_dim_index(D), "L2 Norm");
260    l2_norm_impl(x, dim)
261}
262
263pub(super) fn l2_norm_impl<const D: usize>(x: Tensor<D>, dim: usize) -> Tensor<D> {
264    x.square().sum_dim(dim).sqrt()
265}
266
267fn is_even_integer(x: f64) -> bool {
268    x.fract() == 0.0 && (x as i64) % 2 == 0
269}
270
271/// Computes ``L(2*n)`` for even integer ``n``.
272///
273/// This lets us skip the abs.
274fn lp_signed_norm<const D: usize>(x: Tensor<D>, p: u32, dim: usize) -> Tensor<D> {
275    x.powi_scalar(p).sum_dim(dim).powf_scalar(1. / (p as f64))
276}
277
278/// Computes the general ``L(p)`` using the generalized method.
279///
280/// This uses no specialized implementations and cannot handle:
281/// * 0.0
282/// * f64::INFINITY,
283/// * f64::NEG_INFINITY,
284fn lp_norm_base<const D: usize>(x: Tensor<D>, p: f64, dim: usize) -> Tensor<D> {
285    x.abs().powf_scalar(p).sum_dim(dim).powf_scalar(1. / p)
286}
287
288/// Computes the L:INFINITY norm of a tensor along a specified dimension.
289///
290/// # Arguments
291///
292/// * `x` - The input tensor.
293/// * `dim` - The dimension to compute the norm over.
294///   Negative dimensions are supported and count from the end.
295///
296/// # Returns
297///
298/// The L:INFINITY norm of the input tensor.
299pub fn max_abs_norm<const D: usize, K>(x: Tensor<D, K>, dim: impl AsIndex) -> Tensor<D, K>
300where
301    K: Ordered,
302{
303    let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Abs Norm");
304    max_abs_norm_impl(x, dim)
305}
306
307fn max_abs_norm_impl<const D: usize, K>(x: Tensor<D, K>, dim: usize) -> Tensor<D, K>
308where
309    K: Ordered,
310{
311    x.max_abs_dim(dim)
312}
313
314/// Computes the L:NEG_INFINITY norm of a tensor along a specified dimension.
315///
316/// # Arguments
317///
318/// * `x` - The input tensor.
319/// * `dim` - The dimension to compute the norm over.
320///   Negative dimensions are supported and count from the end.
321///
322/// # Returns
323///
324/// The L:NEG_INFINITY norm of the input tensor.
325pub fn min_abs_norm<const D: usize, K>(x: Tensor<D, K>, dim: impl AsIndex) -> Tensor<D, K>
326where
327    K: Ordered,
328{
329    let dim = unwrap_dim_index(dim.try_dim_index(D), "Min Abs Norm");
330    min_abs_norm_impl(x, dim)
331}
332
333fn min_abs_norm_impl<const D: usize, K>(x: Tensor<D, K>, dim: usize) -> Tensor<D, K>
334where
335    K: Ordered,
336{
337    x.abs().min_dim(dim)
338}