argmin_math/
lib.rs

1// Copyright 2018-2024 argmin developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! argmin-math provides mathematics related abstractions needed in argmin. It supports
9//! implementations of these abstractions for basic `Vec`s and for the `ndarray`, `nalgebra`,
10//! and `faer` linear algebra libraries. The traits can of course also be implemented
11//! for your own types to make them compatible with argmin.
12//!
13//! For an introduction on how to use argmin, please also have a look at the
14//! [book](https://www.argmin-rs.org/book/).
15//!
16//! # Usage
17//!
18//! Add the following line to your dependencies list:
19//!
20//! ```toml
21//! [dependencies]
22#![doc = concat!("argmin-math = \"", env!("CARGO_PKG_VERSION"), "\"")]
23//! ```
24//!
25//! This will activate the `primitives` and `vec` features. For other backends see the section
26//! below.
27//!
28//! ## Features
29//!
30//! Support for the various backends can be switched on via features. Please read this section
31//! carefully to the end before choosing a backend.
32//!
33//! ### Default features
34//!
35//! | Feature                | Default | Comment                                               |
36//! |------------------------|---------|-------------------------------------------------------|
37//! | `primitives`           | yes     | basic integer and floating point types                |
38//! | `vec`                  | yes     | `Vec`s (basic functionality)                          |
39//!
40//! ### `ndarray`
41//!
42//! | Feature                         | Default | Comment                                                            |
43//! |---------------------------------|---------|--------------------------------------------------------------------|
44//! | `ndarray_latest`                | no      | latest supported version                                           |
45//! | `ndarray_latest-nolinalg`       | no      | latest supported version without `ndarray-linalg`                  |
46//! | `ndarray_v0_15`                 | no      | version 0.15 with ndarray-linalg 0.16                              |
47//! | `ndarray_v0_15-nolinalg`        | no      | version 0.15 without `ndarray-linalg`                              |
48//! | `ndarray_v0_14-nolinalg`        | no      | version 0.14 without `ndarray-linalg`                              |
49//! | `ndarray_v0_13-nolinalg`        | no      | version 0.13 without `ndarray-linalg`                              |
50//!
51//! Note that the `*-nolinalg*` features do NOT pull in `ndarray-linalg` as a dependency. This
52//! avoids linking against a BLAS library. This will however disable the implementation of
53//! `ArgminInv`, meaning that any solver which requires the matrix inverse will not work with the
54//! `ndarray` backend. It is recommended to use the `*-nolinalg*` options if the matrix inverse is
55//! not needed in order to keep the compilation times low and avoid problems when linking against a
56//! BLAS library.
57//!
58//! Using the `ndarray_*` features with `ndarray-linalg` support may require to explicitly choose
59//! the `ndarray-linalg` BLAS backend in your `Cargo.toml` (see the [`ndarray-linalg` documentation
60//! for details](https://github.com/rust-ndarray/ndarray-linalg)):
61//!
62//! ```toml
63//! ndarray-linalg = { version = "<appropriate_version>", features = ["<linalg_backend>"] }
64//! ```
65//!
66//! ### `nalgebra`
67//!
68//! | Feature                | Default | Comment                                  |
69//! |------------------------|---------|------------------------------------------|
70//! | `nalgebra_latest`      | no      | latest supported version                 |
71//! | `nalgebra_v0_34`       | no      | version 0.34                             |
72//! | `nalgebra_v0_33`       | no      | version 0.33                             |
73//! | `nalgebra_v0_32`       | no      | version 0.32                             |
74//! | `nalgebra_v0_31`       | no      | version 0.31                             |
75//! | `nalgebra_v0_30`       | no      | version 0.30                             |
76//! | `nalgebra_v0_29`       | no      | version 0.29                             |
77//!
78//! ### `faer`
79//!
80//! | Feature                | Default | Comment                                  |
81//! |------------------------|---------|------------------------------------------|
82//! | `faer_latest`          | no      | latest supported version                 |
83//! | `faer_v0_21`           | no      | version 0.21                             |
84//! | `faer_v0_20`           | no      | version 0.20                             |
85//!
86//! ## Choosing a backend
87//!
88//! It is not possible to activate two versions of the same backend.
89//!
90//! The features labeled `*latest*` are an alias for the most recent supported version of the
91//! respective backend. It is however recommended to explicitly specify the desired version instead
92//! of using any of the `*latest*` features (see section about semantic versioning below).
93//!
94//! The default features `primitives` and `vec` can be turned off in order to only compile the
95//! trait definitions. If another backend is chosen, `primitives` will automatically be turned on
96//! again.
97//!
98//! ### Example
99//!
100//! Activate support for the latest supported `ndarray` version:
101//!
102//! ```toml
103//! [dependencies]
104#![doc = concat!("argmin-math = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"ndarray_latest\"] }")]
105//! ```
106//!
107//! # Semantic versioning
108//!
109//! This crate follows semantic versioning. Adding a new backend or a new version of a backend is
110//! not considered a breaking change. However, your code may still break if you use any of the
111//! features containing `*latest*`. It is therefore recommended to specify the actual version of the
112//! backend you are using.
113//!
114//! # Contributing
115//!
116//! You found a bug? Your favorite backend is not supported? Feel free to open an issue or ideally
117//! submit a PR.
118//!
119//! # License
120//!
121//! Licensed under either of
122//!
123//!   * Apache License, Version 2.0,
124//!     ([LICENSE-APACHE](https://github.com/argmin-rs/argmin/blob/main/LICENSE-APACHE) or
125//!     <http://www.apache.org/licenses/LICENSE-2.0>)
126//!   * MIT License ([LICENSE-MIT](https://github.com/argmin-rs/argmin/blob/main/LICENSE-MIT) or
127//!     <http://opensource.org/licenses/MIT>)
128//!
129//! at your option.
130//!
131//! ## Contribution
132//!
133//! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
134//! in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above,
135//! without any additional terms or conditions.
136
137#![warn(missing_docs)]
138// Explicitly disallow EQ comparison of floats. (This clippy lint is denied by default; however,
139// this is just to make sure that it will always stay this way.)
140#![deny(clippy::float_cmp)]
141
142cfg_if::cfg_if! {
143    if #[cfg(feature = "nalgebra_0_34")] {
144        extern crate nalgebra_0_34 as nalgebra;
145        trait Allocator<T, R, C = nalgebra::U1>: nalgebra::allocator::Allocator<R, C>
146        where
147            R: nalgebra::Dim,
148            C: nalgebra::Dim,
149        {}
150        impl<T, R, C, U> Allocator<T, R, C> for U
151        where
152            U: nalgebra::allocator::Allocator<R, C>,
153            R: nalgebra::Dim,
154            C: nalgebra::Dim,
155        {}
156        trait SameShapeAllocator<T, R1, C1, R2, C2>: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>
157        where
158            R1: nalgebra::Dim,
159            C1: nalgebra::Dim,
160            R2: nalgebra::Dim,
161            C2: nalgebra::Dim,
162            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
163        {}
164        impl<T, R1, C1, R2, C2, U> SameShapeAllocator<T, R1, C1, R2, C2> for U
165        where
166            U: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>,
167            R1: nalgebra::Dim,
168            C1: nalgebra::Dim,
169            R2: nalgebra::Dim,
170            C2: nalgebra::Dim,
171            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
172        {}
173        use nalgebra::{
174            ClosedAddAssign as ClosedAdd,
175            ClosedSubAssign as ClosedSub,
176            ClosedDivAssign as ClosedDiv,
177            ClosedMulAssign as ClosedMul,
178        };
179    } else if #[cfg(feature = "nalgebra_0_33")] {
180        extern crate nalgebra_0_33 as nalgebra;
181        trait Allocator<T, R, C = nalgebra::U1>: nalgebra::allocator::Allocator<R, C>
182        where
183            R: nalgebra::Dim,
184            C: nalgebra::Dim,
185        {}
186        impl<T, R, C, U> Allocator<T, R, C> for U
187        where
188            U: nalgebra::allocator::Allocator<R, C>,
189            R: nalgebra::Dim,
190            C: nalgebra::Dim,
191        {}
192        trait SameShapeAllocator<T, R1, C1, R2, C2>: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>
193        where
194            R1: nalgebra::Dim,
195            C1: nalgebra::Dim,
196            R2: nalgebra::Dim,
197            C2: nalgebra::Dim,
198            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
199        {}
200        impl<T, R1, C1, R2, C2, U> SameShapeAllocator<T, R1, C1, R2, C2> for U
201        where
202            U: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>,
203            R1: nalgebra::Dim,
204            C1: nalgebra::Dim,
205            R2: nalgebra::Dim,
206            C2: nalgebra::Dim,
207            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
208        {}
209        use nalgebra::{
210            ClosedAddAssign as ClosedAdd,
211            ClosedSubAssign as ClosedSub,
212            ClosedDivAssign as ClosedDiv,
213            ClosedMulAssign as ClosedMul,
214        };
215    } else if #[cfg(feature = "nalgebra_0_32")] {
216        extern crate nalgebra_0_32 as nalgebra;
217        use nalgebra::allocator::{Allocator, SameShapeAllocator};
218        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
219    } else if #[cfg(feature = "nalgebra_0_31")] {
220        extern crate nalgebra_0_31 as nalgebra;
221        use nalgebra::allocator::{Allocator, SameShapeAllocator};
222        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
223    } else if #[cfg(feature = "nalgebra_0_30")] {
224        extern crate nalgebra_0_30 as nalgebra;
225        use nalgebra::allocator::{Allocator, SameShapeAllocator};
226        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
227    } else if #[cfg(feature = "nalgebra_0_29")] {
228        extern crate nalgebra_0_29 as nalgebra;
229        use nalgebra::allocator::{Allocator, SameShapeAllocator};
230        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
231    }
232}
233
234cfg_if::cfg_if! {
235    if #[cfg(feature = "ndarray_0_16")] {
236        extern crate ndarray_0_16 as ndarray;
237    } else if #[cfg(feature = "ndarray_0_15")] {
238        extern crate ndarray_0_15 as ndarray;
239    } else if #[cfg(feature = "ndarray_0_14")]  {
240        extern crate ndarray_0_14 as ndarray;
241    } else if #[cfg(feature = "ndarray_0_13")]  {
242        extern crate ndarray_0_13 as ndarray;
243    }
244}
245
246cfg_if::cfg_if! {
247    if #[cfg(feature = "ndarray-linalg_0_17")] {
248        extern crate ndarray_linalg_0_17 as ndarray_linalg;
249    } else if #[cfg(feature = "ndarray-linalg_0_16")] {
250        extern crate ndarray_linalg_0_16 as ndarray_linalg;
251    }
252}
253
254cfg_if::cfg_if! {
255    if #[cfg(feature = "num-complex_0_2")] {
256        extern crate num_complex_0_2 as num_complex;
257    } else if #[cfg(feature = "num-complex_0_3")] {
258        extern crate num_complex_0_3 as num_complex;
259    } else if #[cfg(feature = "num-complex_0_4")] {
260        extern crate num_complex_0_4 as num_complex;
261    }
262}
263
264cfg_if::cfg_if! {
265    if #[cfg(feature = "faer_v0_20")] {
266        extern crate faer_0_20 as faer;
267    } else if #[cfg(feature = "faer_v0_21")] {
268        extern crate faer_0_21 as faer;
269    }
270}
271
272#[cfg(feature = "primitives")]
273mod primitives;
274#[cfg(feature = "primitives")]
275#[allow(unused_imports)]
276pub use crate::primitives::*;
277
278#[cfg(feature = "ndarray_all")]
279mod ndarray_m;
280#[cfg(feature = "ndarray_all")]
281#[allow(unused_imports)]
282pub use crate::ndarray_m::*;
283
284#[cfg(feature = "nalgebra_all")]
285mod nalgebra_m;
286#[cfg(feature = "nalgebra_all")]
287#[allow(unused_imports)]
288pub use crate::nalgebra_m::*;
289
290#[cfg(feature = "vec")]
291mod vec;
292#[cfg(feature = "vec")]
293#[allow(unused_imports)]
294pub use crate::vec::*;
295
296cfg_if! {
297    // faer has significant breaking changes between 0.20 and 0.21, which
298    // makes this trickery necessary
299    if #[cfg(feature = "faer_v0_20")] {
300        mod faer_m_0_20;
301        use faer_m_0_20 as faer_m;
302    } else if #[cfg(feature = "faer_v0_21")] {
303        extern crate faer_traits_0_21 as faer_traits;
304        mod faer_m_0_21;
305        use faer_m_0_21 as faer_m;
306    }
307}
308
309#[cfg(feature = "faer_all")]
310#[allow(unused_imports)]
311pub use crate::faer_m::*;
312
313#[cfg(test)]
314#[cfg(feature = "faer_all")]
315mod faer_tests;
316
317// Re-export of types appearing in the api as recommended here: https://www.lurklurk.org/effective-rust/re-export.html
318pub use anyhow::Error;
319use cfg_if::cfg_if;
320#[cfg(feature = "rand")]
321pub use rand::Rng;
322
323/// Dot/scalar product of `T` and `self`
324pub trait ArgminDot<T, U> {
325    /// Dot/scalar product of `T` and `self`
326    fn dot(&self, other: &T) -> U;
327}
328
329/// Dot/scalar product of `T` and `self` weighted by W (p^TWv)
330pub trait ArgminWeightedDot<T, U, V> {
331    /// Dot/scalar product of `T` and `self`
332    fn weighted_dot(&self, w: &V, vec: &T) -> U;
333}
334
335/// Return param vector of all zeros (for now, this is a hack. It should be done better)
336pub trait ArgminZero {
337    /// Return zero(s)
338    fn zero() -> Self;
339}
340
341/// Return the conjugate
342pub trait ArgminConj {
343    /// Return conjugate
344    #[must_use]
345    fn conj(&self) -> Self;
346}
347
348/// Zero for dynamically sized objects
349pub trait ArgminZeroLike {
350    /// Return zero(s)
351    #[must_use]
352    fn zero_like(&self) -> Self;
353}
354
355/// Identity matrix
356pub trait ArgminEye {
357    /// Identity matrix of size `n`
358    fn eye(n: usize) -> Self;
359    /// Identity matrix of same size as `self`
360    #[must_use]
361    fn eye_like(&self) -> Self;
362}
363
364/// Add a `T` to `self`
365pub trait ArgminAdd<T, U> {
366    /// Add a `T` to `self`
367    fn add(&self, other: &T) -> U;
368}
369
370/// Subtract a `T` from `self`
371pub trait ArgminSub<T, U> {
372    /// Subtract a `T` from `self`
373    fn sub(&self, other: &T) -> U;
374}
375
376/// (Pointwise) Multiply a `T` with `self`
377pub trait ArgminMul<T, U> {
378    /// (Pointwise) Multiply a `T` with `self`
379    fn mul(&self, other: &T) -> U;
380}
381
382/// (Pointwise) Divide a `T` by `self`
383pub trait ArgminDiv<T, U> {
384    /// (Pointwise) Divide a `T` by `self`
385    fn div(&self, other: &T) -> U;
386}
387
388/// Add a `T` scaled by an `U` to `self`
389pub trait ArgminScaledAdd<T, U, V> {
390    /// Add a `T` scaled by an `U` to `self`
391    fn scaled_add(&self, factor: &U, vec: &T) -> V;
392}
393
394/// Subtract a `T` scaled by an `U` from `self`
395pub trait ArgminScaledSub<T, U, V> {
396    /// Subtract a `T` scaled by an `U` from `self`
397    fn scaled_sub(&self, factor: &U, vec: &T) -> V;
398}
399
400/// Compute the l1-norm (`U`) of `self`
401pub trait ArgminL1Norm<U> {
402    /// Compute the l1-norm (`U`) of `self`
403    fn l1_norm(&self) -> U;
404}
405
406/// Compute the l2-norm (`U`) of `self`
407pub trait ArgminL2Norm<U> {
408    /// Compute the l2-norm (`U`) of `self`
409    fn l2_norm(&self) -> U;
410}
411
412// Sub-optimal: self is moved. ndarray however offers array views...
413/// Return the transpose (`U`) of `self`
414pub trait ArgminTranspose<U> {
415    /// Transpose
416    fn t(self) -> U;
417}
418
419/// Compute the inverse (`T`) of `self`
420pub trait ArgminInv<T> {
421    /// Compute the inverse
422    fn inv(&self) -> Result<T, Error>;
423}
424
425/// Create a random number
426#[cfg(feature = "rand")]
427pub trait ArgminRandom {
428    /// Get a random element between min and max,
429    fn rand_from_range<R: Rng>(min: &Self, max: &Self, rng: &mut R) -> Self;
430}
431
432/// Minimum and Maximum of type `T`
433pub trait ArgminMinMax {
434    /// Select piecewise minimum
435    fn min(x: &Self, y: &Self) -> Self;
436    /// Select piecewise maximum
437    fn max(x: &Self, y: &Self) -> Self;
438}
439
440/// Returns a number that represents the sign of `self`.
441pub trait ArgminSignum {
442    /// Returns a number that represents the sign of `self`.
443    fn signum(self) -> Self;
444}