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}