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_33`       | no      | version 0.33                             |
72//! | `nalgebra_v0_32`       | no      | version 0.32                             |
73//! | `nalgebra_v0_31`       | no      | version 0.31                             |
74//! | `nalgebra_v0_30`       | no      | version 0.30                             |
75//! | `nalgebra_v0_29`       | no      | version 0.29                             |
76//!
77//! ### `faer`
78//!
79//! | Feature                | Default | Comment                                  |
80//! |------------------------|---------|------------------------------------------|
81//! | `faer_latest`          | no      | latest supported version                 |
82//! | `faer_v0_21`           | no      | version 0.21                             |
83//! | `faer_v0_20`           | no      | version 0.20                             |
84//!
85//! ## Choosing a backend
86//!
87//! It is not possible to activate two versions of the same backend.
88//!
89//! The features labeled `*latest*` are an alias for the most recent supported version of the
90//! respective backend. It is however recommended to explicitly specify the desired version instead
91//! of using any of the `*latest*` features (see section about semantic versioning below).
92//!
93//! The default features `primitives` and `vec` can be turned off in order to only compile the
94//! trait definitions. If another backend is chosen, `primitives` will automatically be turned on
95//! again.
96//!
97//! ### Example
98//!
99//! Activate support for the latest supported `ndarray` version:
100//!
101//! ```toml
102//! [dependencies]
103#![doc = concat!("argmin-math = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"ndarray_latest\"] }")]
104//! ```
105//!
106//! # Semantic versioning
107//!
108//! This crate follows semantic versioning. Adding a new backend or a new version of a backend is
109//! not considered a breaking change. However, your code may still break if you use any of the
110//! features containing `*latest*`. It is therefore recommended to specify the actual version of the
111//! backend you are using.
112//!
113//! # Contributing
114//!
115//! You found a bug? Your favorite backend is not supported? Feel free to open an issue or ideally
116//! submit a PR.
117//!
118//! # License
119//!
120//! Licensed under either of
121//!
122//!   * Apache License, Version 2.0,
123//!     ([LICENSE-APACHE](https://github.com/argmin-rs/argmin/blob/main/LICENSE-APACHE) or
124//!     <http://www.apache.org/licenses/LICENSE-2.0>)
125//!   * MIT License ([LICENSE-MIT](https://github.com/argmin-rs/argmin/blob/main/LICENSE-MIT) or
126//!     <http://opensource.org/licenses/MIT>)
127//!
128//! at your option.
129//!
130//! ## Contribution
131//!
132//! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
133//! in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above,
134//! without any additional terms or conditions.
135
136#![warn(missing_docs)]
137// Explicitly disallow EQ comparison of floats. (This clippy lint is denied by default; however,
138// this is just to make sure that it will always stay this way.)
139#![deny(clippy::float_cmp)]
140
141cfg_if::cfg_if! {
142    if #[cfg(feature = "nalgebra_0_33")] {
143        extern crate nalgebra_0_33 as nalgebra;
144        trait Allocator<T, R, C = nalgebra::U1>: nalgebra::allocator::Allocator<R, C>
145        where
146            R: nalgebra::Dim,
147            C: nalgebra::Dim,
148        {}
149        impl<T, R, C, U> Allocator<T, R, C> for U
150        where
151            U: nalgebra::allocator::Allocator<R, C>,
152            R: nalgebra::Dim,
153            C: nalgebra::Dim,
154        {}
155        trait SameShapeAllocator<T, R1, C1, R2, C2>: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>
156        where
157            R1: nalgebra::Dim,
158            C1: nalgebra::Dim,
159            R2: nalgebra::Dim,
160            C2: nalgebra::Dim,
161            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
162        {}
163        impl<T, R1, C1, R2, C2, U> SameShapeAllocator<T, R1, C1, R2, C2> for U
164        where
165            U: nalgebra::allocator::SameShapeAllocator<R1, C1, R2, C2>,
166            R1: nalgebra::Dim,
167            C1: nalgebra::Dim,
168            R2: nalgebra::Dim,
169            C2: nalgebra::Dim,
170            nalgebra::constraint::ShapeConstraint: nalgebra::constraint::SameNumberOfRows<R1,R2> + nalgebra::constraint::SameNumberOfColumns<C1,C2>,
171        {}
172        use nalgebra::{
173            ClosedAddAssign as ClosedAdd,
174            ClosedSubAssign as ClosedSub,
175            ClosedDivAssign as ClosedDiv,
176            ClosedMulAssign as ClosedMul,
177        };
178    } else if #[cfg(feature = "nalgebra_0_32")] {
179        extern crate nalgebra_0_32 as nalgebra;
180        use nalgebra::allocator::{Allocator, SameShapeAllocator};
181        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
182    } else if #[cfg(feature = "nalgebra_0_31")] {
183        extern crate nalgebra_0_31 as nalgebra;
184        use nalgebra::allocator::{Allocator, SameShapeAllocator};
185        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
186    } else if #[cfg(feature = "nalgebra_0_30")] {
187        extern crate nalgebra_0_30 as nalgebra;
188        use nalgebra::allocator::{Allocator, SameShapeAllocator};
189        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
190    } else if #[cfg(feature = "nalgebra_0_29")] {
191        extern crate nalgebra_0_29 as nalgebra;
192        use nalgebra::allocator::{Allocator, SameShapeAllocator};
193        use nalgebra::{ClosedAdd, ClosedSub, ClosedDiv, ClosedMul};
194    }
195}
196
197cfg_if::cfg_if! {
198    if #[cfg(feature = "ndarray_0_16")] {
199        extern crate ndarray_0_16 as ndarray;
200    } else if #[cfg(feature = "ndarray_0_15")] {
201        extern crate ndarray_0_15 as ndarray;
202    } else if #[cfg(feature = "ndarray_0_14")]  {
203        extern crate ndarray_0_14 as ndarray;
204    } else if #[cfg(feature = "ndarray_0_13")]  {
205        extern crate ndarray_0_13 as ndarray;
206    }
207}
208
209cfg_if::cfg_if! {
210    if #[cfg(feature = "ndarray-linalg_0_17")] {
211        extern crate ndarray_linalg_0_17 as ndarray_linalg;
212    } else if #[cfg(feature = "ndarray-linalg_0_16")] {
213        extern crate ndarray_linalg_0_16 as ndarray_linalg;
214    }
215}
216
217cfg_if::cfg_if! {
218    if #[cfg(feature = "num-complex_0_2")] {
219        extern crate num_complex_0_2 as num_complex;
220    } else if #[cfg(feature = "num-complex_0_3")] {
221        extern crate num_complex_0_3 as num_complex;
222    } else if #[cfg(feature = "num-complex_0_4")] {
223        extern crate num_complex_0_4 as num_complex;
224    }
225}
226
227cfg_if::cfg_if! {
228    if #[cfg(feature = "faer_v0_20")] {
229        extern crate faer_0_20 as faer;
230    } else if #[cfg(feature = "faer_v0_21")] {
231        extern crate faer_0_21 as faer;
232    }
233}
234
235#[cfg(feature = "primitives")]
236mod primitives;
237#[cfg(feature = "primitives")]
238#[allow(unused_imports)]
239pub use crate::primitives::*;
240
241#[cfg(feature = "ndarray_all")]
242mod ndarray_m;
243#[cfg(feature = "ndarray_all")]
244#[allow(unused_imports)]
245pub use crate::ndarray_m::*;
246
247#[cfg(feature = "nalgebra_all")]
248mod nalgebra_m;
249#[cfg(feature = "nalgebra_all")]
250#[allow(unused_imports)]
251pub use crate::nalgebra_m::*;
252
253#[cfg(feature = "vec")]
254mod vec;
255#[cfg(feature = "vec")]
256#[allow(unused_imports)]
257pub use crate::vec::*;
258
259cfg_if! {
260    // faer has significant breaking changes between 0.20 and 0.21, which
261    // makes this trickery necessary
262    if #[cfg(feature = "faer_v0_20")] {
263        mod faer_m_0_20;
264        use faer_m_0_20 as faer_m;
265    } else if #[cfg(feature = "faer_v0_21")] {
266        extern crate faer_traits_0_21 as faer_traits;
267        mod faer_m_0_21;
268        use faer_m_0_21 as faer_m;
269    }
270}
271
272#[cfg(feature = "faer_all")]
273#[allow(unused_imports)]
274pub use crate::faer_m::*;
275
276#[cfg(test)]
277#[cfg(feature = "faer_all")]
278mod faer_tests;
279
280// Re-export of types appearing in the api as recommended here: https://www.lurklurk.org/effective-rust/re-export.html
281pub use anyhow::Error;
282use cfg_if::cfg_if;
283#[cfg(feature = "rand")]
284pub use rand::Rng;
285
286/// Dot/scalar product of `T` and `self`
287pub trait ArgminDot<T, U> {
288    /// Dot/scalar product of `T` and `self`
289    fn dot(&self, other: &T) -> U;
290}
291
292/// Dot/scalar product of `T` and `self` weighted by W (p^TWv)
293pub trait ArgminWeightedDot<T, U, V> {
294    /// Dot/scalar product of `T` and `self`
295    fn weighted_dot(&self, w: &V, vec: &T) -> U;
296}
297
298/// Return param vector of all zeros (for now, this is a hack. It should be done better)
299pub trait ArgminZero {
300    /// Return zero(s)
301    fn zero() -> Self;
302}
303
304/// Return the conjugate
305pub trait ArgminConj {
306    /// Return conjugate
307    #[must_use]
308    fn conj(&self) -> Self;
309}
310
311/// Zero for dynamically sized objects
312pub trait ArgminZeroLike {
313    /// Return zero(s)
314    #[must_use]
315    fn zero_like(&self) -> Self;
316}
317
318/// Identity matrix
319pub trait ArgminEye {
320    /// Identity matrix of size `n`
321    fn eye(n: usize) -> Self;
322    /// Identity matrix of same size as `self`
323    #[must_use]
324    fn eye_like(&self) -> Self;
325}
326
327/// Add a `T` to `self`
328pub trait ArgminAdd<T, U> {
329    /// Add a `T` to `self`
330    fn add(&self, other: &T) -> U;
331}
332
333/// Subtract a `T` from `self`
334pub trait ArgminSub<T, U> {
335    /// Subtract a `T` from `self`
336    fn sub(&self, other: &T) -> U;
337}
338
339/// (Pointwise) Multiply a `T` with `self`
340pub trait ArgminMul<T, U> {
341    /// (Pointwise) Multiply a `T` with `self`
342    fn mul(&self, other: &T) -> U;
343}
344
345/// (Pointwise) Divide a `T` by `self`
346pub trait ArgminDiv<T, U> {
347    /// (Pointwise) Divide a `T` by `self`
348    fn div(&self, other: &T) -> U;
349}
350
351/// Add a `T` scaled by an `U` to `self`
352pub trait ArgminScaledAdd<T, U, V> {
353    /// Add a `T` scaled by an `U` to `self`
354    fn scaled_add(&self, factor: &U, vec: &T) -> V;
355}
356
357/// Subtract a `T` scaled by an `U` from `self`
358pub trait ArgminScaledSub<T, U, V> {
359    /// Subtract a `T` scaled by an `U` from `self`
360    fn scaled_sub(&self, factor: &U, vec: &T) -> V;
361}
362
363/// Compute the l1-norm (`U`) of `self`
364pub trait ArgminL1Norm<U> {
365    /// Compute the l1-norm (`U`) of `self`
366    fn l1_norm(&self) -> U;
367}
368
369/// Compute the l2-norm (`U`) of `self`
370pub trait ArgminL2Norm<U> {
371    /// Compute the l2-norm (`U`) of `self`
372    fn l2_norm(&self) -> U;
373}
374
375// Sub-optimal: self is moved. ndarray however offers array views...
376/// Return the transpose (`U`) of `self`
377pub trait ArgminTranspose<U> {
378    /// Transpose
379    fn t(self) -> U;
380}
381
382/// Compute the inverse (`T`) of `self`
383pub trait ArgminInv<T> {
384    /// Compute the inverse
385    fn inv(&self) -> Result<T, Error>;
386}
387
388/// Create a random number
389#[cfg(feature = "rand")]
390pub trait ArgminRandom {
391    /// Get a random element between min and max,
392    fn rand_from_range<R: Rng>(min: &Self, max: &Self, rng: &mut R) -> Self;
393}
394
395/// Minimum and Maximum of type `T`
396pub trait ArgminMinMax {
397    /// Select piecewise minimum
398    fn min(x: &Self, y: &Self) -> Self;
399    /// Select piecewise maximum
400    fn max(x: &Self, y: &Self) -> Self;
401}
402
403/// Returns a number that represents the sign of `self`.
404pub trait ArgminSignum {
405    /// Returns a number that represents the sign of `self`.
406    fn signum(self) -> Self;
407}