mdarray_linalg/matvec.rs
1//! Basic vector and matrix-vector operations, including `Ax`, `Ax + βy`, Givens rotations, argmax, and rank-1 updates
2//!
3//! # Matrix-Vector Operations
4//!
5//! ```rust
6//! use mdarray::tensor;
7//! use mdarray_linalg::prelude::*;
8//! use mdarray_linalg::Naive;
9//!
10//! // Create a 3x3 matrix and a vector
11//! let a = tensor![[1., 2., 3.],
12//! [4., 5., 6.],
13//! [7., 8., 9.]];
14//! let x = tensor![1., 1., 1.];
15//!
16//! // Basic matrix-vector multiplication: y = A·x
17//! let y = Naive.matvec(&a, &x).eval();
18//! assert_eq!(y, tensor![6., 15., 24.]);
19//!
20//! // Scaled operation: y = 2·A·x
21//! let y_scaled = Naive.matvec(&a, &x).scale(2.).eval();
22//! assert_eq!(y_scaled, tensor![12., 30., 48.]);
23//!
24//! // Write result to existing vector: y := α·A·x
25//! let mut y_write = tensor![0., 0., 0.];
26//! Naive.matvec(&a, &x).scale(2.).write(&mut y_write);
27//! assert_eq!(y_write, tensor![12., 30., 48.]);
28//!
29//! // Add to vector: y := A·x + y
30//! let mut y_add = tensor![1., 1., 1.];
31//! Naive.matvec(&a, &x).add_to_vec(&mut y_add);
32//! assert_eq!(y_add, tensor![7., 16., 25.]);
33//!
34//! // Scaled addition: y := α·A·x + β·y
35//! let mut y_axpy = tensor![1., 1., 1.];
36//! Naive.matvec(&a, &x).add_to_scaled_vec(&mut y_axpy, 2.);
37//! assert_eq!(y_axpy, tensor![8., 17., 26.]);
38//! ```
39//!
40//! # Outer Products and Rank-1 Updates
41//!
42//! ```rust
43//! use mdarray::tensor;
44//! use mdarray_linalg::prelude::*;
45//! use mdarray_linalg::Naive;
46//!
47//! // Create two vectors
48//! let x = tensor![1., 2.];
49//! let y = tensor![1., 10., 100.];
50//!
51//! // Basic outer product: A = x ⊗ y
52//! let a = Naive.outer(&x, &y).eval();
53//! assert_eq!(a, tensor![[1., 10., 100.],
54//! [2., 20., 200.]]);
55//!
56//! // Scaled outer product: A = β·(x ⊗ y)
57//! let a_scaled = Naive.outer(&x, &y).scale(2.).eval();
58//! assert_eq!(a_scaled, tensor![[2., 20., 200.],
59//! [4., 40., 400.]]);
60//!
61//! // Write to existing matrix: A := β·(x ⊗ y)
62//! let mut a_write = tensor![[0., 0., 0.],
63//! [0., 0., 0.]];
64//! Naive.outer(&x, &y).scale(2.).write(&mut a_write);
65//! assert_eq!(a_write, tensor![[2., 20., 200.],
66//! [4., 40., 400.]]);
67//!
68//! // Rank-1 update: A := β·(x ⊗ y) + A
69//! let mut a_update = tensor![[1., 1., 1.],
70//! [1., 1., 1.]];
71//! Naive.outer(&x, &y).scale(2.).add_to(&mut a_update);
72//! assert_eq!(a_update, tensor![[3., 21., 201.],
73//! [5., 41., 401.]]);
74//! ```
75//!
76//! # Complex Number Support
77//!
78//! ```rust
79//! use mdarray::tensor;
80//! use mdarray_linalg::prelude::*;
81//! use mdarray_linalg::Naive;
82//! use num_complex::Complex64;
83//!
84//! // Complex outer product
85//! let x = tensor![Complex64::new(1., 1.), Complex64::new(2., 0.)];
86//! let y = tensor![Complex64::new(1., 0.), Complex64::new(0., 1.)];
87//! let a = Naive.outer(&x, &y).eval();
88//! assert_eq!(a[[0, 0]], Complex64::new(1., 1.));
89//! assert_eq!(a[[0, 1]], Complex64::new(-1., 1.));
90//!
91//! ```
92//! # Argmax
93//!
94//! ```rust
95//! use mdarray::tensor;
96//! use mdarray_linalg::prelude::*;
97//! use mdarray_linalg::Naive;
98//!
99//! // Find index of maximum value in 1D array
100//! let x = tensor![1., 5., 3., 8., 2.];
101//! let idx = Naive.argmax(&x).unwrap();
102//! assert_eq!(idx, vec![3]); // Maximum is at index 3
103//!
104//! // Find index in 2D array (returns multi-dimensional index)
105//! let a = tensor![[0., 1., 2.],
106//! [3., 4., 5.]];
107//! let idx = Naive.argmax(&a.view(.., ..).into_dyn()).unwrap();
108//! assert_eq!(idx, vec![1, 2]); // Maximum is at position [1, 2]
109//!
110//! // Find element with largest absolute value
111//! let y = tensor![1., -6., 3., -2., 5.];
112//! let idx = Naive.argmax_abs(&y).unwrap();
113//! assert_eq!(idx, vec![1]); // -6 has largest absolute value
114//!
115//! // Write result to reusable buffer
116//! let mut output = Vec::new();
117//! let success = Naive.argmax_write(&x, &mut output);
118//! assert!(success);
119//! assert_eq!(output, vec![3]);
120//! ```
121//! # Vector Operations
122//!
123//! ```rust
124//! use mdarray::tensor;
125//! use mdarray_linalg::prelude::*;
126//! use mdarray_linalg::Naive;
127//! use num_complex::Complex64;
128//!
129//! // Scaled vector addition: y := α·x + y
130//! let x = tensor![1., 2., 3.];
131//! let mut y = tensor![1., 1., 1.];
132//! Naive.add_to_scaled(2.0, &x, &mut y);
133//! assert_eq!(y, tensor![3., 5., 7.]); // y = 2·x + y
134//!
135//! // Dot product: ∑xᵢyᵢ
136//! let x = tensor![1., 2., 3.];
137//! let y = tensor![2., 4., 6.];
138//! let result = Naive.dot(&x, &y);
139//! assert_eq!(result, 28.0); // 1*2 + 2*4 + 3*6 = 28
140//!
141//! // Conjugated dot product with complex numbers: ∑(conj(xᵢ)·yᵢ)
142//! let x = tensor![Complex64::new(1., 2.), Complex64::new(2., 3.)];
143//! let y = tensor![Complex64::new(3., 4.), Complex64::new(4., 5.)];
144//! let result = Naive.dotc(&x, &y);
145//! // conj(1+2i)*(3+4i) + conj(2+3i)*(4+5i)
146//! let expected = x[[0]].conj() * y[[0]] + x[[1]].conj() * y[[1]];
147//! assert_eq!(result, expected);
148//!
149//! // L2 norm (Euclidean): √(∑|xᵢ|²)
150//! let x = tensor![3., 4.];
151//! let norm = Naive.norm2(&x);
152//! assert_eq!(norm, 5.0); // √(9 + 16) = 5
153//!
154//! // L1 norm (Manhattan): ∑|xᵢ|
155//! let x = tensor![Complex64::new(1., 2.), Complex64::new(2., 3.)];
156//! let norm = Naive.norm1(&x);
157//! // |1+2i| + |2+3i| = (|1|+|2|) + (|2|+|3|) = 8
158//! assert_eq!(norm, 8.0);
159//! ```
160use mdarray::{Array, Dim, Layout, Shape, Slice};
161
162/// Matrix-vector multiplication and transformations
163pub trait MatVec<T, D0: Dim, D1: Dim> {
164 fn matvec<'a, La, Lx>(
165 &self,
166 a: &'a Slice<T, (D0, D1), La>,
167 x: &'a Slice<T, (D1,), Lx>,
168 ) -> impl MatVecBuilder<'a, T, La, Lx, D0, D1>
169 where
170 La: Layout,
171 Lx: Layout;
172}
173
174/// Builder interface for configuring matrix-vector operations
175pub trait MatVecBuilder<'a, T, La, Lx, D0: Dim, D1: Dim>
176where
177 La: Layout,
178 Lx: Layout,
179 T: 'a,
180 La: 'a,
181 Lx: 'a,
182{
183 /// `α := α·α'`
184 fn scale(self, alpha: T) -> Self;
185
186 /// Returns `α·A·x`
187 fn eval(self) -> Array<T, (D0,)>;
188
189 /// `y := α·A·x`
190 fn write<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>);
191
192 /// `y := α·A·x + y`
193 fn add_to_vec<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>);
194
195 /// `y := α·A·x + β·y`
196 fn add_to_scaled_vec<Ly: Layout>(self, y: &mut Slice<T, (D0,), Ly>, beta: T);
197}
198
199/// Vector operations and basic linear algebra utilities
200pub trait VecOps<T, D1: Dim> {
201 /// Real scalar type used for norm results and real rotation coefficients.
202 ///
203 /// This type is chosen by the backend implementation for the scalar type `T`.
204 type Real;
205
206 /// Accumulate a scaled vector: `y := α·x + y`
207 fn add_to_scaled<Lx: Layout, Ly: Layout>(
208 &self,
209 alpha: T,
210 x: &Slice<T, (D1,), Lx>,
211 y: &mut Slice<T, (D1,), Ly>,
212 );
213
214 /// Dot product: `∑xᵢyᵢ`
215 fn dot<Lx: Layout, Ly: Layout>(&self, x: &Slice<T, (D1,), Lx>, y: &Slice<T, (D1,), Ly>) -> T;
216
217 /// Conjugated dot product: `∑conj(xᵢ) * yᵢ` (BLAS convention)
218 fn dotc<Lx: Layout, Ly: Layout>(&self, x: &Slice<T, (D1,), Lx>, y: &Slice<T, (D1,), Ly>) -> T;
219
220 /// L2 norm: `√(∑|xᵢ|²)`
221 fn norm2<Lx: Layout>(&self, x: &Slice<T, (D1,), Lx>) -> Self::Real;
222
223 /// L1 norm (Manhattan) for complex numbers: `∑(|re(xᵢ)| + |im(xᵢ)|)`.
224 /// For real numbers this reduces to `∑|xᵢ|`.
225 fn norm1<Lx: Layout>(&self, x: &Slice<T, (D1,), Lx>) -> Self::Real;
226
227 /// Givens rotation
228 fn rot<Lx: Layout, Ly: Layout>(
229 &self,
230 x: &mut Slice<T, (D1,), Lx>,
231 y: &mut Slice<T, (D1,), Ly>,
232 c: Self::Real,
233 s: T,
234 );
235}
236
237/// Argmax for tensors of any rank.
238///
239/// Implementations define the ordering and magnitude semantics they support for `T`;
240/// backend impl bounds should state any Rust scalar traits they rely on.
241pub trait Argmax<T> {
242 fn argmax_write<Lx: Layout, S: Shape>(
243 &self,
244 x: &Slice<T, S, Lx>,
245 output: &mut Vec<usize>,
246 ) -> bool;
247
248 fn argmax_abs_write<Lx: Layout, S: Shape>(
249 &self,
250 x: &Slice<T, S, Lx>,
251 output: &mut Vec<usize>,
252 ) -> bool;
253
254 /// Index of max xᵢ (argmaxᵢ xᵢ)
255 fn argmax<Lx: Layout, S: Shape>(&self, x: &Slice<T, S, Lx>) -> Option<Vec<usize>>;
256
257 /// Index of max |xᵢ| (argmaxᵢ |xᵢ|)
258 fn argmax_abs<Lx: Layout, S: Shape>(&self, x: &Slice<T, S, Lx>) -> Option<Vec<usize>>;
259}
260
261/// Outer product and rank-1 update
262pub trait Outer<T, Dx: Dim, Dy: Dim> {
263 fn outer<'a, Lx, Ly>(
264 &self,
265 x: &'a Slice<T, (Dx,), Lx>,
266 y: &'a Slice<T, (Dy,), Ly>,
267 ) -> impl OuterBuilder<'a, T, Lx, Ly, Dx, Dy>
268 where
269 Lx: Layout,
270 Ly: Layout;
271}
272
273/// Builder interface for configuring outer product and rank-1 update
274pub trait OuterBuilder<'a, T, Lx, Ly, Dx: Dim, Dy: Dim>
275where
276 Lx: Layout,
277 Ly: Layout,
278 T: 'a,
279 Lx: 'a,
280 Ly: 'a,
281{
282 /// `α := α·α'`
283 fn scale(self, alpha: T) -> Self;
284
285 /// Returns `α·xy`
286 fn eval(self) -> Array<T, (Dx, Dy)>;
287
288 /// `a := α·xy`
289 fn write<La: Layout>(self, a: &mut Slice<T, (Dx, Dy), La>);
290
291 /// Rank-1 update: `A := α·x·yᵀ + A`
292 fn add_to<La: Layout>(self, a: &mut Slice<T, (Dx, Dy), La>);
293}