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}