Skip to main content

faer/linalg/svd/
mod.rs

1//! low level implementation of the svd of a matrix
2//!
3//! the svd of a matrix $A$ of shape $(m, n)$ is a decomposition into three
4//! components $U$, $S$, and $V$, such that:
5//!
6//! - $U$ has shape $(m, m)$ and is a unitary matrix
7//! - $V$ has shape $(n, n)$ and is a unitary matrix
8//! - $S$ has shape $(m, n)$ and is zero everywhere except the main diagonal
9//! - and finally:
10//!
11//! $$A = U S V^H$$
12use crate::assert;
13use crate::internal_prelude::*;
14use bidiag::BidiagParams;
15use linalg::qr::no_pivoting::factor::QrParams;
16/// bidiagonalization
17pub mod bidiag;
18pub(crate) mod bidiag_svd;
19/// whether the singular vectors should be computed
20#[derive(Copy, Clone, Debug, PartialEq, Eq)]
21pub enum ComputeSvdVectors {
22	/// do not compute singular vectors
23	No,
24	/// compute the first $\min(\text{nrows}, \text{ncols})$ singular vectors
25	Thin,
26	/// compute singular vectors
27	Full,
28}
29/// svd error
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31pub enum SvdError {
32	/// reached max iterations
33	NoConvergence,
34}
35/// svd tuning parameters
36#[derive(Debug, Copy, Clone)]
37pub struct SvdParams {
38	/// bidiagonalization parameters
39	pub bidiag: BidiagParams,
40	/// $QR$ parameters
41	pub qr: QrParams,
42	/// threshold at which the implementation should stop recursing
43	pub recursion_threshold: usize,
44	/// threshold at which parallelism should be disabled
45	pub qr_ratio_threshold: f64,
46	#[doc(hidden)]
47	pub non_exhaustive: NonExhaustive,
48}
49impl<T: ComplexField> Auto<T> for SvdParams {
50	fn auto() -> Self {
51		Self {
52			recursion_threshold: 128,
53			qr_ratio_threshold: 11.0 / 6.0,
54			bidiag: auto!(T),
55			qr: auto!(T),
56			non_exhaustive: NonExhaustive(()),
57		}
58	}
59}
60fn svd_imp_scratch<T: ComplexField>(
61	m: usize,
62	n: usize,
63	compute_u: ComputeSvdVectors,
64	compute_v: ComputeSvdVectors,
65	bidiag_svd_scratch: fn(
66		n: usize,
67		compute_u: bool,
68		compute_v: bool,
69		par: Par,
70		params: SvdParams,
71	) -> StackReq,
72	params: SvdParams,
73	par: Par,
74) -> StackReq {
75	assert!(m >= n);
76	let householder_block_size =
77		linalg::qr::no_pivoting::factor::recommended_block_size::<T>(m, n);
78	let bid = temp_mat_scratch::<T>(m, n);
79	let householder_left = temp_mat_scratch::<T>(householder_block_size, n);
80	let householder_right = temp_mat_scratch::<T>(householder_block_size, n);
81	let compute_bidiag =
82		bidiag::bidiag_in_place_scratch::<T>(m, n, par, params.bidiag.into());
83	let diag = temp_mat_scratch::<T>(n, 1);
84	let subdiag = diag;
85	let compute_ub = compute_v != ComputeSvdVectors::No;
86	let compute_vb = compute_u != ComputeSvdVectors::No;
87	let u_b = temp_mat_scratch::<T>(if compute_ub { n + 1 } else { 2 }, n + 1);
88	let v_b = temp_mat_scratch::<T>(n, if compute_vb { n } else { 0 });
89	let compute_bidiag_svd =
90		bidiag_svd_scratch(n, compute_ub, compute_vb, par, params);
91	let apply_householder_u = linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(
92		m,
93		householder_block_size,
94		match compute_u {
95			ComputeSvdVectors::No => 0,
96			ComputeSvdVectors::Thin => n,
97			ComputeSvdVectors::Full => m,
98		},
99	);
100	let apply_householder_v = linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(
101		n - 1,
102		householder_block_size,
103		match compute_v {
104			ComputeSvdVectors::No => 0,
105			_ => n,
106		},
107	);
108	StackReq::all_of(&[
109		bid,
110		householder_left,
111		householder_right,
112		StackReq::any_of(&[
113			compute_bidiag,
114			StackReq::all_of(&[
115				diag,
116				subdiag,
117				u_b,
118				v_b,
119				StackReq::any_of(&[
120					compute_bidiag_svd,
121					StackReq::all_of(&[
122						apply_householder_u,
123						apply_householder_v,
124					]),
125				]),
126			]),
127		]),
128	])
129}
130fn bidiag_cplx_svd_scratch<T: ComplexField>(
131	n: usize,
132	compute_u: bool,
133	compute_v: bool,
134	par: Par,
135	params: SvdParams,
136) -> StackReq {
137	StackReq::all_of(&[
138		temp_mat_scratch::<T>(n, 1).array(4),
139		temp_mat_scratch::<T::Real>(n + 1, if compute_u { n + 1 } else { 0 }),
140		temp_mat_scratch::<T::Real>(n, if compute_v { n } else { 0 }),
141		bidiag_real_svd_scratch::<T::Real>(
142			n, compute_u, compute_v, par, params,
143		),
144	])
145}
146fn bidiag_real_svd_scratch<T: RealField>(
147	n: usize,
148	compute_u: bool,
149	compute_v: bool,
150	par: Par,
151	params: SvdParams,
152) -> StackReq {
153	if n < params.recursion_threshold {
154		StackReq::EMPTY
155	} else {
156		StackReq::all_of(&[
157			temp_mat_scratch::<T>(2, if compute_u { 0 } else { n + 1 }),
158			bidiag_svd::divide_and_conquer_scratch::<T>(
159				n,
160				params.recursion_threshold,
161				compute_u,
162				compute_v,
163				par,
164			),
165		])
166	}
167}
168fn compute_bidiag_cplx_svd<T: ComplexField>(
169	mut diag: ColMut<'_, T, usize, ContiguousFwd>,
170	subdiag: ColMut<'_, T, usize, ContiguousFwd>,
171	mut u: Option<MatMut<'_, T>>,
172	mut v: Option<MatMut<'_, T>>,
173	params: SvdParams,
174	par: Par,
175	stack: &mut MemStack,
176) -> Result<(), SvdError> {
177	let n = diag.nrows();
178	let (mut diag_real, stack) =
179		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
180	let (mut subdiag_real, stack) =
181		unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
182	let (mut u_real, stack) = unsafe {
183		temp_mat_uninit::<T::Real, _, _>(
184			n + 1,
185			if u.is_some() { n + 1 } else { 0 },
186			stack,
187		)
188	};
189	let (mut v_real, stack) = unsafe {
190		temp_mat_uninit::<T::Real, _, _>(
191			n,
192			if v.is_some() { n } else { 0 },
193			stack,
194		)
195	};
196	let (mut col_mul, stack) =
197		unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
198	let (mut row_mul, stack) =
199		unsafe { temp_mat_uninit::<T, _, _>(n - 1, 1, stack) };
200	let mut u_real = u.rb().map(|_| u_real.as_mat_mut());
201	let mut v_real = v.rb().map(|_| v_real.as_mat_mut());
202	let mut diag_real = diag_real
203		.as_mat_mut()
204		.col_mut(0)
205		.try_as_col_major_mut()
206		.unwrap();
207	let mut subdiag_real = subdiag_real
208		.as_mat_mut()
209		.col_mut(0)
210		.try_as_col_major_mut()
211		.unwrap();
212	let mut col_mul = col_mul.as_mat_mut().col_mut(0);
213	let mut row_mul = row_mul.as_mat_mut().col_mut(0);
214	let normalized = |x: T| {
215		if x == zero() {
216			one()
217		} else {
218			let norm1 = x.real().abs().fmax(x.imag().abs());
219			// a subnormal component scale has an infinite reciprocal; rescale it the
220			// way reference `dlarfg` does instead of dividing by a zero product
221			let ref shift = if norm1 >= min_positive::<T::Real>() {
222				from_f64::<T::Real>(1.0)
223			} else {
224				from_f64::<T::Real>(4503599627370496.0)
225			};
226			let y = x.mul_real(shift).mul_real((&norm1 * shift).recip());
227			y.mul_real(y.abs().recip())
228		}
229	};
230	let magnitude = |x: &T| {
231		// `abs` squares components scaled by `sqrt(min_positive)` or
232		// `sqrt(max_positive)`, which flushes a magnitude below `min_positive`
233		// to zero and overflows above about `max_positive / 2`. Outside
234		// `[min_positive, 1 / min_positive]`, rescale by that exact power of
235		// two first; other entries keep the historical `abs` bit for bit.
236		let sml = min_positive::<T::Real>();
237		let big = sml.recip();
238		let m = x.real().abs().fmax(x.imag().abs());
239		if m < sml && m != zero() {
240			x.mul_real(&big).abs() * sml
241		} else if m > big && m.is_finite() {
242			x.mul_real(&sml).abs() * big
243		} else {
244			x.abs()
245		}
246	};
247	let mut col_normalized = normalized(diag[0].conj());
248	col_mul[0] = col_normalized.copy();
249	diag_real[0] = magnitude(&diag[0]);
250	subdiag_real[n - 1] = zero();
251	for i in 1..n {
252		let row_normalized =
253			normalized((&subdiag[i - 1] * col_normalized).conj());
254		subdiag_real[i - 1] = magnitude(&subdiag[i - 1]);
255		row_mul[i - 1] = row_normalized.conj();
256		col_normalized = normalized((&diag[i] * row_normalized).conj());
257		diag_real[i] = magnitude(&diag[i]);
258		col_mul[i] = col_normalized.copy();
259	}
260	compute_bidiag_real_svd(
261		diag_real.rb_mut(),
262		subdiag_real.rb_mut(),
263		u_real.rb_mut(),
264		v_real.rb_mut(),
265		params,
266		par,
267		stack,
268	)?;
269	for i in 0..n {
270		diag[i] = diag_real[i].to_cplx();
271	}
272	let u_real = u_real.rb();
273	let v_real = v_real.rb();
274	if let (Some(mut u), Some(u_real)) = (u.rb_mut(), u_real) {
275		z!(u.rb_mut().row_mut(0), u_real.row(0))
276			.for_each(|uz!(u, r)| *u = r.to_cplx());
277		z!(u.rb_mut().row_mut(n), u_real.row(n))
278			.for_each(|uz!(u, r)| *u = r.to_cplx());
279		for j in 0..u.ncols() {
280			let mut u = u.rb_mut().col_mut(j).subrows_mut(1, n - 1);
281			let u_real = u_real.rb().col(j).subrows(1, n - 1);
282			z!(u.rb_mut(), u_real, row_mul.rb())
283				.for_each(|uz!(u, re, f)| *u = f.mul_real(re));
284		}
285	}
286	if let (Some(mut v), Some(v_real)) = (v.rb_mut(), v_real) {
287		for j in 0..v.ncols() {
288			let mut v = v.rb_mut().col_mut(j);
289			let v_real = v_real.rb().col(j);
290			z!(v.rb_mut(), v_real, col_mul.rb())
291				.for_each(|uz!(v, re, f)| *v = f.mul_real(re));
292		}
293	}
294	Ok(())
295}
296fn compute_bidiag_real_svd<T: RealField>(
297	mut diag: ColMut<'_, T, usize, ContiguousFwd>,
298	mut subdiag: ColMut<'_, T, usize, ContiguousFwd>,
299	mut u: Option<MatMut<'_, T, usize, usize>>,
300	mut v: Option<MatMut<'_, T, usize, usize>>,
301	params: SvdParams,
302	par: Par,
303	stack: &mut MemStack,
304) -> Result<(), SvdError> {
305	let n = diag.nrows();
306	for i in 0..n {
307		if !(diag[i].is_finite() && subdiag[i].is_finite()) {
308			return Err(SvdError::NoConvergence);
309		}
310	}
311	if n < params.recursion_threshold {
312		if let Some(mut u) = u.rb_mut() {
313			u.fill(zero());
314			u.diagonal_mut().fill(one());
315		}
316		if let Some(mut v) = v.rb_mut() {
317			v.fill(zero());
318			v.diagonal_mut().fill(one());
319		}
320		bidiag_svd::qr_algorithm(
321			diag.rb_mut(),
322			subdiag.rb_mut(),
323			u.rb_mut().map(|u| u.submatrix_mut(0, 0, n, n)),
324			v.rb_mut(),
325		)?;
326		return Ok(());
327	} else {
328		let (mut u2, stack) = unsafe {
329			temp_mat_uninit::<T::Real, _, _>(
330				2,
331				if u.is_some() { 0 } else { n + 1 },
332				stack,
333			)
334		};
335		bidiag_svd::divide_and_conquer(
336			diag.as_row_shape_mut(n),
337			subdiag.as_row_shape_mut(n),
338			match u {
339				Some(u) => bidiag_svd::MatU::Full(u),
340				None => bidiag_svd::MatU::TwoRowsStorage(u2.as_mat_mut()),
341			},
342			v.map(|m| m.as_shape_mut(n, n)),
343			par,
344			stack,
345			params.recursion_threshold,
346		)
347	}
348}
349/// Workspace for [`bidiagonal_svd`], including the padded
350/// lower-bidiagonal storage used by the existing divide-and-conquer
351/// implementation.
352///
353/// # Examples
354/// ```
355/// use faer::Par;
356/// use faer::linalg::svd::bidiagonal_svd_scratch;
357/// let _buffer = dyn_stack::MemBuffer::new(bidiagonal_svd_scratch::<f64>(
358/// 	3,
359/// 	Par::Seq,
360/// 	Default::default(),
361/// ));
362/// ```
363pub fn bidiagonal_svd_scratch<T: ComplexField>(
364	dim: usize,
365	par: Par,
366	params: Spec<SvdParams, T>,
367) -> StackReq {
368	let n = dim;
369	if n == 0 {
370		return StackReq::EMPTY;
371	}
372	StackReq::all_of(&[
373		temp_mat_scratch::<T>(n, 1).array(2),
374		temp_mat_scratch::<T>(n + 1, n + 1),
375		temp_mat_scratch::<T>(n, n),
376		bidiag_cplx_svd_scratch::<T>(n, true, true, par, *params),
377	])
378}
379/// Computes a square bidiagonal SVD `B = U * diag(s) * V.adjoint()`.
380/// `diag` is overwritten with nonnegative, descending singular values (with
381/// zero imaginary part). `offdiag` has length `n.saturating_sub(1)` and is
382/// unchanged. `upper` selects the superdiagonal rather than the subdiagonal.
383/// Both `u` and `v` are `n` by `n`; the output is **V**, not its adjoint.
384/// Real and complex entries are supported via the existing phase reduction.
385///
386/// # Panics
387/// Panics on incompatible dimensions or insufficient scratch.
388///
389/// # Errors
390/// Returns [`SvdError::NoConvergence`] for nonfinite input, iteration
391/// failure, or a result that would not be finite (for example when a complex
392/// entry's modulus overflows). Finite input never yields `Ok` with nonfinite
393/// output. Outputs are unspecified on error; `offdiag` is always unchanged.
394///
395/// # Examples
396/// ```
397/// use dyn_stack::{MemBuffer, MemStack};
398/// use faer::linalg::svd::*;
399/// use faer::{Mat, Par};
400/// let mut d = [3.0f64, -2.0];
401/// let (mut u, mut v) = (Mat::zeros(2, 2), Mat::zeros(2, 2));
402/// let mut mem = MemBuffer::new(bidiagonal_svd_scratch::<f64>(
403/// 	2,
404/// 	Par::Seq,
405/// 	Default::default(),
406/// ));
407/// bidiagonal_svd(
408/// 	&mut d,
409/// 	&[0.0],
410/// 	true,
411/// 	u.as_mut(),
412/// 	v.as_mut(),
413/// 	Par::Seq,
414/// 	MemStack::new(&mut mem),
415/// 	Default::default(),
416/// )
417/// .unwrap();
418/// assert_eq!(d, [3.0, 2.0]);
419/// ```
420pub fn bidiagonal_svd<T: ComplexField>(
421	diag: &mut [T],
422	offdiag: &[T],
423	upper: bool,
424	mut u: MatMut<'_, T>,
425	mut v: MatMut<'_, T>,
426	par: Par,
427	stack: &mut MemStack,
428	params: Spec<SvdParams, T>,
429) -> Result<(), SvdError> {
430	let n = diag.len();
431	assert!(offdiag.len() == n.saturating_sub(1));
432	assert!(
433		u.nrows() == n && u.ncols() == n && v.nrows() == n && v.ncols() == n
434	);
435	if !diag.iter().chain(offdiag).all(|x| x.is_finite()) {
436		return Err(SvdError::NoConvergence);
437	}
438	if n == 0 {
439		return Ok(());
440	}
441	// SAFETY: Every entry is written below before the kernel reads the
442	// diagonal.
443	let (mut d, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
444	let (mut e, stack) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
445	// SAFETY: The phase-reduction kernel fully writes each factor on success;
446	// its error path returns before either factor is read here.
447	let (mut left, stack) =
448		unsafe { temp_mat_uninit::<T, _, _>(n + 1, n + 1, stack) };
449	// SAFETY: Same full-overwrite contract as the left factor above.
450	let (mut right, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
451	let mut d = d.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
452	let mut e = e.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
453	for i in 0..n {
454		d[i] = if upper {
455			diag[i].conj()
456		} else {
457			diag[i].copy()
458		};
459		if i + 1 < n {
460			e[i] = if upper {
461				offdiag[i].conj()
462			} else {
463				offdiag[i].copy()
464			};
465		}
466	}
467	compute_bidiag_cplx_svd(
468		d.rb_mut(),
469		e.rb_mut(),
470		Some(left.as_mat_mut()),
471		Some(right.as_mat_mut()),
472		*params,
473		par,
474		stack,
475	)?;
476	// Backstop: finite input never reports success with nonfinite output.
477	if !(d.rb().is_all_finite()
478		&& left.as_mat_ref().submatrix(0, 0, n, n).is_all_finite()
479		&& right.as_mat_ref().is_all_finite())
480	{
481		return Err(SvdError::NoConvergence);
482	}
483	for i in 0..n {
484		diag[i] = d[i].copy();
485	}
486	if upper {
487		u.copy_from(right.as_mat_ref());
488		v.copy_from(left.as_mat_ref().submatrix(0, 0, n, n));
489	} else {
490		u.copy_from(left.as_mat_ref().submatrix(0, 0, n, n));
491		v.copy_from(right.as_mat_ref());
492	}
493	Ok(())
494}
495/// bidiag -> divide conquer svd / qr algo
496fn svd_imp<T: ComplexField>(
497	matrix: MatRef<'_, T>,
498	s: ColMut<'_, T>,
499	u: Option<MatMut<'_, T>>,
500	v: Option<MatMut<'_, T>>,
501	bidiag_svd: fn(
502		diag: ColMut<'_, T, usize, ContiguousFwd>,
503		subdiag: ColMut<'_, T, usize, ContiguousFwd>,
504		u: Option<MatMut<'_, T, usize, usize>>,
505		v: Option<MatMut<'_, T, usize, usize>>,
506		params: SvdParams,
507		par: Par,
508		stack: &mut MemStack,
509	) -> Result<(), SvdError>,
510	par: Par,
511	stack: &mut MemStack,
512	params: SvdParams,
513) -> Result<(), SvdError> {
514	assert!(matrix.nrows() >= matrix.ncols());
515	let m = matrix.nrows();
516	let n = matrix.ncols();
517	let bs = linalg::qr::no_pivoting::factor::recommended_block_size::<T>(m, n);
518	let (mut bid, stack) = unsafe { temp_mat_uninit::<T, _, _>(m, n, stack) };
519	let mut bid = bid.as_mat_mut();
520	let (mut Hl, stack) = unsafe { temp_mat_uninit::<T, _, _>(bs, n, stack) };
521	let (mut Hr, stack) =
522		unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
523	let mut Hl = Hl.as_mat_mut();
524	let mut Hr = Hr.as_mat_mut();
525	bid.copy_from(matrix);
526	bidiag::bidiag_in_place(
527		bid.rb_mut(),
528		Hl.rb_mut(),
529		Hr.rb_mut(),
530		par,
531		stack,
532		params.bidiag.into(),
533	);
534	let (mut diag, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
535	let (mut subdiag, stack) =
536		unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
537	let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
538	let mut subdiag = subdiag
539		.as_mat_mut()
540		.col_mut(0)
541		.try_as_col_major_mut()
542		.unwrap();
543	let (mut ub, stack) = unsafe {
544		temp_mat_uninit::<T, _, _>(
545			n + 1,
546			if v.is_some() { n + 1 } else { 0 },
547			stack,
548		)
549	};
550	let (mut vb, stack) = unsafe {
551		temp_mat_uninit::<T, _, _>(n, if u.is_some() { n } else { 0 }, stack)
552	};
553	let mut ub = ub.as_mat_mut();
554	let mut vb = vb.as_mat_mut();
555	for i in 0..n {
556		diag[i] = bid[(i, i)].conj();
557		if i + 1 < n {
558			subdiag[i] = bid[(i, i + 1)].conj();
559		} else {
560			subdiag[i] = zero();
561		}
562	}
563	bidiag_svd(
564		diag.rb_mut(),
565		subdiag.rb_mut(),
566		v.rb().map(|_| ub.rb_mut()),
567		u.rb().map(|_| vb.rb_mut()),
568		params,
569		par,
570		stack,
571	)?;
572	{ s }.copy_from(diag);
573	if let Some(mut u) = u {
574		let ncols = u.ncols();
575		u.rb_mut().submatrix_mut(0, 0, n, n).copy_from(vb.rb());
576		u.rb_mut().submatrix_mut(n, 0, m - n, ncols).fill(zero());
577		u.rb_mut().submatrix_mut(0, n, n, ncols - n).fill(zero());
578		u.rb_mut()
579			.submatrix_mut(n, n, ncols - n, ncols - n)
580			.diagonal_mut()
581			.fill(one());
582		linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(bid.rb(), Hl.rb(), Conj::No, u, par, stack);
583	}
584	if let Some(mut v) = v {
585		v.copy_from(ub.rb().submatrix(0, 0, n, n));
586		for j in 1..n {
587			for i in 0..j {
588				bid[(j, i)] = bid[(i, j)].copy();
589			}
590		}
591		linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
592			bid.rb().submatrix(1, 0, n - 1, n - 1),
593			Hr.rb(),
594			Conj::Yes,
595			v.subrows_mut(1, n - 1),
596			par,
597			stack,
598		);
599	}
600	Ok(())
601}
602fn compute_squareish_svd<T: ComplexField>(
603	matrix: MatRef<'_, T>,
604	s: ColMut<'_, T>,
605	u: Option<MatMut<'_, T>>,
606	v: Option<MatMut<'_, T>>,
607	par: Par,
608	stack: &mut MemStack,
609	params: SvdParams,
610) -> Result<(), SvdError> {
611	if const { T::IS_REAL } {
612		svd_imp::<T::Real>(
613			unsafe { core::mem::transmute(matrix) },
614			unsafe { core::mem::transmute(s) },
615			unsafe { core::mem::transmute(u) },
616			unsafe { core::mem::transmute(v) },
617			compute_bidiag_real_svd::<T::Real>,
618			par,
619			stack,
620			params,
621		)
622	} else {
623		svd_imp::<T>(
624			matrix,
625			s,
626			u,
627			v,
628			compute_bidiag_cplx_svd::<T>,
629			par,
630			stack,
631			params,
632		)
633	}
634}
635/// computes the layout of the workspace required to compute a matrix's svd
636pub fn svd_scratch<T: ComplexField>(
637	nrows: usize,
638	ncols: usize,
639	compute_u: ComputeSvdVectors,
640	compute_v: ComputeSvdVectors,
641	par: Par,
642	params: Spec<SvdParams, T>,
643) -> StackReq {
644	let params = params.config;
645	let mut m = nrows;
646	let mut n = ncols;
647	let mut compute_u = compute_u;
648	let mut compute_v = compute_v;
649	if n > m {
650		core::mem::swap(&mut m, &mut n);
651		core::mem::swap(&mut compute_u, &mut compute_v);
652	}
653	if n == 0 {
654		return StackReq::EMPTY;
655	}
656	let bidiag_svd_scratch = if const { T::IS_REAL } {
657		bidiag_real_svd_scratch::<T::Real>
658	} else {
659		bidiag_cplx_svd_scratch::<T>
660	};
661	if m as f64 / n as f64 <= params.qr_ratio_threshold {
662		svd_imp_scratch::<T>(
663			m,
664			n,
665			compute_u,
666			compute_v,
667			bidiag_svd_scratch,
668			params,
669			par,
670		)
671	} else {
672		let bs =
673			linalg::qr::no_pivoting::factor::recommended_block_size::<T>(m, n);
674		StackReq::all_of(&[
675			temp_mat_scratch::<T>(m, n),
676			temp_mat_scratch::<T>(bs, n),
677			StackReq::any_of(&[
678				StackReq::all_of(&[
679					temp_mat_scratch::<T>(n, n),
680					svd_imp_scratch::<T>(n, n, compute_u, compute_v, bidiag_svd_scratch, params, par),
681				]),
682				linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(
683					m,
684					bs,
685					match compute_u {
686						ComputeSvdVectors::No => 0,
687						ComputeSvdVectors::Thin => n,
688						ComputeSvdVectors::Full => m,
689					},
690				),
691			]),
692		])
693	}
694}
695/// computes the svd of $A$, with the singular vectors being omitted, thin or
696/// full
697///
698/// the singular are stored in $S$, and the singular vectors in $U$ and $V$ such
699/// that the singular values are sorted in nonincreasing order
700pub fn svd<T: ComplexField>(
701	A: MatRef<'_, T>,
702	s: DiagMut<'_, T>,
703	u: Option<MatMut<'_, T>>,
704	v: Option<MatMut<'_, T>>,
705	par: Par,
706	stack: &mut MemStack,
707	params: Spec<SvdParams, T>,
708) -> Result<(), SvdError> {
709	let params = params.config;
710	let (m, n) = A.shape();
711	let size = Ord::min(m, n);
712	assert!(s.dim() == size);
713	let s = s.column_vector_mut();
714	if let Some(u) = u.rb() {
715		assert!(all(
716			u.nrows() == A.nrows(),
717			any(u.ncols() == A.nrows(), u.ncols() == size),
718		));
719	}
720	if let Some(v) = v.rb() {
721		assert!(all(
722			v.nrows() == A.ncols(),
723			any(v.ncols() == A.ncols(), v.ncols() == size),
724		));
725	}
726	#[cfg(feature = "perf-warn")]
727	match (u.rb(), v.rb()) {
728		(Some(matrix), _) | (_, Some(matrix)) => {
729			if matrix.row_stride().unsigned_abs() != 1
730				&& crate::__perf_warn!(QR_WARN)
731			{
732				if matrix.col_stride().unsigned_abs() == 1 {
733					log::warn!(
734						target : "faer_perf",
735						"SVD prefers column-major singular vector matrices. Found row-major matrix."
736					);
737				} else {
738					log::warn!(
739						target : "faer_perf",
740						"SVD prefers column-major singular vector matrices. Found matrix with generic strides."
741					);
742				}
743			}
744		},
745		_ => {},
746	}
747	let mut u = u;
748	let mut v = v;
749	let mut matrix = A;
750	let do_transpose = n > m;
751	if do_transpose {
752		matrix = matrix.transpose();
753		core::mem::swap(&mut u, &mut v)
754	}
755	let (m, n) = matrix.shape();
756	if n == 0 {
757		if let Some(mut u) = u {
758			u.fill(zero());
759			u.rb_mut().diagonal_mut().fill(one());
760		}
761		return Ok(());
762	}
763	if m as f64 / n as f64 <= params.qr_ratio_threshold {
764		compute_squareish_svd(
765			matrix,
766			s,
767			u.rb_mut(),
768			v.rb_mut(),
769			par,
770			stack,
771			params,
772		)?;
773	} else {
774		let bs =
775			linalg::qr::no_pivoting::factor::recommended_block_size::<T>(m, n);
776		let (mut qr, stack) =
777			unsafe { temp_mat_uninit::<T, _, _>(m, n, stack) };
778		let mut qr = qr.as_mat_mut();
779		let (mut householder, stack) =
780			unsafe { temp_mat_uninit::<T, _, _>(bs, n, stack) };
781		let mut householder = householder.as_mat_mut();
782		{
783			qr.copy_from(matrix.rb());
784			linalg::qr::no_pivoting::factor::qr_in_place(
785				qr.rb_mut(),
786				householder.rb_mut(),
787				par,
788				stack,
789				params.qr.into(),
790			);
791		}
792		{
793			let (mut r, stack) =
794				unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
795			let mut r = r.as_mat_mut();
796			z!(r.rb_mut()).for_each_triangular_lower(
797				linalg::zip::Diag::Skip,
798				|uz!(dst)| *dst = zero(),
799			);
800			z!(r.rb_mut(), qr.rb().submatrix(0, 0, n, n))
801				.for_each_triangular_upper(
802					linalg::zip::Diag::Include,
803					|uz!(dst, src)| *dst = src.copy(),
804				);
805			compute_squareish_svd(
806				r.rb(),
807				s,
808				u.rb_mut().map(|u| u.submatrix_mut(0, 0, n, n)),
809				v.rb_mut(),
810				par,
811				stack,
812				params,
813			)?;
814		}
815		if let Some(mut u) = u.rb_mut() {
816			u.rb_mut().subrows_mut(n, m - n).fill(zero());
817			if u.ncols() == m {
818				// the extra columns are `Q * [0; I]`, so their top block has
819				// to start at zero instead of at the caller's old entries
820				u.rb_mut().submatrix_mut(0, n, n, m - n).fill(zero());
821				u.rb_mut()
822					.submatrix_mut(n, n, m - n, m - n)
823					.diagonal_mut()
824					.fill(one());
825			}
826			linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
827				qr.rb(),
828				householder.rb(),
829				Conj::No,
830				u.rb_mut(),
831				par,
832				stack,
833			);
834		}
835	}
836	if do_transpose {
837		if let Some(u) = u.rb_mut() {
838			z!(u).for_each(|uz!(u)| *u = u.conj())
839		}
840		if let Some(v) = v.rb_mut() {
841			z!(v).for_each(|uz!(v)| *v = v.conj())
842		}
843	}
844	Ok(())
845}
846/// computes the layout of the workspace required to compute a matrix's
847/// pseudoinverse, given the svd
848pub fn pseudoinverse_from_svd_scratch<T: ComplexField>(
849	nrows: usize,
850	ncols: usize,
851	par: Par,
852) -> StackReq {
853	_ = par;
854	let size = Ord::min(nrows, ncols);
855	StackReq::all_of(&[
856		temp_mat_scratch::<T>(nrows, size),
857		temp_mat_scratch::<T>(ncols, size),
858	])
859}
860/// computes a self-adjoint matrix's pseudoinverse, given the svd factors $S$,
861/// $U$ and $V$
862pub fn pseudoinverse_from_svd<T: ComplexField>(
863	pinv: MatMut<'_, T>,
864	s: DiagRef<'_, T>,
865	u: MatRef<'_, T>,
866	v: MatRef<'_, T>,
867	par: Par,
868	stack: &mut MemStack,
869) {
870	pseudoinverse_from_svd_with_tolerance(
871		pinv,
872		s,
873		u,
874		v,
875		zero(),
876		eps::<T::Real>()
877			* from_f64::<T::Real>(Ord::max(u.nrows(), v.nrows()) as f64),
878		par,
879		stack,
880	);
881}
882/// computes a self-adjoint matrix's pseudoinverse, given the svd factors $S$,
883/// $U$ and $V$, and tolerance parameters for determining zero singular values
884pub fn pseudoinverse_from_svd_with_tolerance<T: ComplexField>(
885	pinv: MatMut<'_, T>,
886	s: DiagRef<'_, T>,
887	u: MatRef<'_, T>,
888	v: MatRef<'_, T>,
889	abs_tol: T::Real,
890	rel_tol: T::Real,
891	par: Par,
892	stack: &mut MemStack,
893) {
894	let mut pinv = pinv;
895	let m = u.nrows();
896	let n = v.nrows();
897	let size = Ord::min(m, n);
898	assert!(all(
899		u.nrows() == m,
900		v.nrows() == n,
901		u.ncols() >= size,
902		v.ncols() >= size,
903		s.dim() >= size
904	));
905	let s = s.column_vector();
906	let u = u.get(.., ..size);
907	let v = v.get(.., ..size);
908	let smax = s.norm_max();
909	let tol = abs_tol.fmax(rel_tol * smax);
910	let (mut u_trunc, stack) =
911		unsafe { temp_mat_uninit::<T, _, _>(m, size, stack) };
912	let (mut vp_trunc, _) =
913		unsafe { temp_mat_uninit::<T, _, _>(n, size, stack) };
914	let mut u_trunc = u_trunc.as_mat_mut();
915	let mut vp_trunc = vp_trunc.as_mat_mut();
916	let mut len = 0;
917	for j in 0..size {
918		let x = s[j].absmax();
919		if x > tol {
920			let ref p = s[j].real().recip();
921			u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
922			z!(vp_trunc.rb_mut().col_mut(len), v.col(j))
923				.for_each(|uz!(dst, src)| *dst = src.mul_real(p));
924			len += 1;
925		}
926	}
927	let u_trunc = u_trunc.get(.., ..len);
928	let vp_trunc = vp_trunc.get(.., ..len);
929	linalg::matmul::matmul(
930		pinv.rb_mut(),
931		Accum::Replace,
932		vp_trunc,
933		u_trunc.adjoint(),
934		one(),
935		par,
936	);
937}
938#[cfg(test)]
939mod tests {
940	use super::*;
941	use crate::assert;
942	use crate::stats::prelude::*;
943	use crate::utils::approx::*;
944	use dyn_stack::MemBuffer;
945	#[track_caller]
946	fn test_svd<T: ComplexField>(mat: MatRef<'_, T>) {
947		let (m, n) = mat.shape();
948		let params = Spec::new(SvdParams {
949			recursion_threshold: 8,
950			qr_ratio_threshold: 1.0,
951			..auto!(T)
952		});
953		let approx_eq = CwiseMat(
954			ApproxEq::<T::Real>::eps()
955				* from_f64::<T::Real>(8.0 * Ord::max(m, n) as f64).sqrt(),
956		);
957		{
958			let mut s = Mat::zeros(m, n);
959			let mut u = Mat::zeros(m, m);
960			let mut v = Mat::zeros(n, n);
961			svd(
962				mat.as_ref(),
963				s.as_mut().diagonal_mut(),
964				Some(u.as_mut()),
965				Some(v.as_mut()),
966				Par::Seq,
967				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
968					m,
969					n,
970					ComputeSvdVectors::Full,
971					ComputeSvdVectors::Full,
972					Par::Seq,
973					params,
974				))),
975				params,
976			)
977			.unwrap();
978			let reconstructed = &u * &s * v.adjoint();
979			assert!(reconstructed ~ mat);
980		}
981		let size = Ord::min(m, n);
982		let mut s = Mat::zeros(size, size);
983		let mut u = Mat::zeros(m, size);
984		let mut v = Mat::zeros(n, size);
985		{
986			svd(
987				mat.as_ref(),
988				s.as_mut().diagonal_mut(),
989				Some(u.as_mut()),
990				Some(v.as_mut()),
991				Par::Seq,
992				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
993					m,
994					n,
995					ComputeSvdVectors::Thin,
996					ComputeSvdVectors::Thin,
997					Par::Seq,
998					params,
999				))),
1000				params,
1001			)
1002			.unwrap();
1003			let reconstructed = &u * &s * v.adjoint();
1004			assert!(reconstructed ~ mat);
1005		}
1006		{
1007			let mut s2 = Mat::zeros(size, size);
1008			let mut u2 = Mat::zeros(m, size);
1009			svd(
1010				mat.as_ref(),
1011				s2.as_mut().diagonal_mut(),
1012				Some(u2.as_mut()),
1013				None,
1014				Par::Seq,
1015				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1016					m,
1017					n,
1018					ComputeSvdVectors::Thin,
1019					ComputeSvdVectors::No,
1020					Par::Seq,
1021					params,
1022				))),
1023				params,
1024			)
1025			.unwrap();
1026			assert!(s2 ~ s);
1027			assert!(u2 ~ u);
1028		}
1029		{
1030			let mut s2 = Mat::zeros(size, size);
1031			let mut v2 = Mat::zeros(n, size);
1032			svd(
1033				mat.as_ref(),
1034				s2.as_mut().diagonal_mut(),
1035				None,
1036				Some(v2.as_mut()),
1037				Par::Seq,
1038				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1039					m,
1040					n,
1041					ComputeSvdVectors::No,
1042					ComputeSvdVectors::Thin,
1043					Par::Seq,
1044					params,
1045				))),
1046				params,
1047			)
1048			.unwrap();
1049			assert!(s2 ~ s);
1050			assert!(v2 ~ v);
1051		}
1052		{
1053			let mut s2 = Mat::zeros(size, size);
1054			svd(
1055				mat.as_ref(),
1056				s2.as_mut().diagonal_mut(),
1057				None,
1058				None,
1059				Par::Seq,
1060				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1061					m,
1062					n,
1063					ComputeSvdVectors::No,
1064					ComputeSvdVectors::No,
1065					Par::Seq,
1066					params,
1067				))),
1068				params,
1069			)
1070			.unwrap();
1071			assert!(s2 ~ s);
1072		}
1073	}
1074	fn check_tiny_split<T: ComplexField>(a: MatRef<'_, T>) {
1075		let (m, n) = a.shape();
1076		let size = Ord::min(m, n);
1077		let compute = |threshold, want_u, want_v| {
1078			let params = Spec::new(SvdParams {
1079				recursion_threshold: threshold,
1080				..auto!(T)
1081			});
1082			let mut s = Col::zeros(size);
1083			let mut u = Mat::zeros(m, if want_u { size } else { 0 });
1084			let mut v = Mat::zeros(n, if want_v { size } else { 0 });
1085			let vectors = |want| {
1086				if want {
1087					ComputeSvdVectors::Thin
1088				} else {
1089					ComputeSvdVectors::No
1090				}
1091			};
1092			svd(
1093				a,
1094				s.as_mut().as_diagonal_mut(),
1095				if want_u { Some(u.as_mut()) } else { None },
1096				if want_v { Some(v.as_mut()) } else { None },
1097				Par::Seq,
1098				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1099					m,
1100					n,
1101					vectors(want_u),
1102					vectors(want_v),
1103					Par::Seq,
1104					params,
1105				))),
1106				params,
1107			)
1108			.unwrap();
1109			(s, u, v)
1110		};
1111		let (reference, _, _) = compute(usize::MAX, true, true);
1112		for (want_u, want_v) in
1113			[(true, true), (false, false), (true, false), (false, true)]
1114		{
1115			let (s, u, v) = compute(128, want_u, want_v);
1116			assert!((&s - &reference).norm_max() < from_f64::<T::Real>(1e-13));
1117			if want_u {
1118				assert!(
1119					(u.adjoint() * &u - Mat::<T>::identity(size, size))
1120						.norm_l2() < from_f64::<T::Real>(1e-13)
1121				);
1122			}
1123			if want_v {
1124				assert!(
1125					(v.adjoint() * &v - Mat::<T>::identity(size, size))
1126						.norm_l2() < from_f64::<T::Real>(1e-13)
1127				);
1128			}
1129			if want_u && want_v {
1130				let back = &u * s.as_diagonal() * v.adjoint();
1131				assert!(
1132					(&back - a).norm_l2() / a.norm_l2()
1133						< from_f64::<T::Real>(1e-13)
1134				);
1135			}
1136		}
1137	}
1138	#[test]
1139	fn test_tiny_diagonal_at_split() {
1140		for n in [128, 256] {
1141			for tiny in [0.0, 1e-15, 1e-13, 1e-10, 1e-6] {
1142				let a = Mat::<f64>::from_fn(n, n, |i, j| {
1143					if (i == j && i < 64) || (j == i + 1 && i < 64) {
1144						1.0
1145					} else if i == 64 && j == 64 {
1146						tiny
1147					} else {
1148						0.0
1149					}
1150				});
1151				check_tiny_split(a.as_ref());
1152				let complex =
1153					Mat::<c64>::from_fn(n, n, |i, j| c64::new(0.0, a[(i, j)]));
1154				check_tiny_split(complex.as_ref());
1155			}
1156		}
1157	}
1158	/// A zero block at a divide-and-conquer split must still hand its parent merge the
1159	/// identity boundary entry that carries the nonzero part of the spectrum, whatever
1160	/// singular vectors the caller asked for (#33).
1161	#[test]
1162	fn test_zero_block_at_split() {
1163		fn check<T: ComplexField>(
1164			n: usize,
1165			threshold: usize,
1166			want: (bool, bool),
1167			tol: f64,
1168		) {
1169			let (want_u, want_v) = want;
1170			let a = Mat::<T>::from_fn(n, n, |i, j| {
1171				if i == n / 2 && j == n / 2 {
1172					one()
1173				} else {
1174					zero()
1175				}
1176			});
1177			let params = Spec::new(SvdParams {
1178				recursion_threshold: threshold,
1179				..auto!(T)
1180			});
1181			let vectors = |want| {
1182				if want {
1183					ComputeSvdVectors::Full
1184				} else {
1185					ComputeSvdVectors::No
1186				}
1187			};
1188			let mut s = Col::<T>::zeros(n);
1189			let mut u = Mat::<T>::zeros(n, if want_u { n } else { 0 });
1190			let mut v = Mat::<T>::zeros(n, if want_v { n } else { 0 });
1191			svd(
1192				a.as_ref(),
1193				s.as_mut().as_diagonal_mut(),
1194				if want_u { Some(u.as_mut()) } else { None },
1195				if want_v { Some(v.as_mut()) } else { None },
1196				Par::Seq,
1197				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1198					n,
1199					n,
1200					vectors(want_u),
1201					vectors(want_v),
1202					Par::Seq,
1203					params,
1204				))),
1205				params,
1206			)
1207			.unwrap();
1208			// `norm_max` skips NaN entries, so finiteness is stated separately
1209			assert!(s.is_all_finite());
1210			if want_u {
1211				assert!(u.is_all_finite());
1212			}
1213			if want_v {
1214				assert!(v.is_all_finite());
1215			}
1216			let expected =
1217				Col::<T>::from_fn(n, |i| if i == 0 { one() } else { zero() });
1218			let tol = from_f64::<T::Real>(tol);
1219			assert!((&s - &expected).norm_max() <= tol.copy());
1220			let identity = Mat::<T>::identity(n, n);
1221			if want_u && want_v {
1222				let back = &u * s.as_diagonal() * v.adjoint();
1223				assert!((&back - &a).norm_max() <= tol.copy());
1224			}
1225			if want_u {
1226				assert!(
1227					(u.adjoint() * &u - &identity).norm_max() <= tol.copy()
1228				);
1229			}
1230			if want_v {
1231				assert!(
1232					(v.adjoint() * &v - &identity).norm_max() <= tol.copy()
1233				);
1234			}
1235		}
1236		for n in [8usize, 16, 32] {
1237			for threshold in [4, 8] {
1238				for want in
1239					[(true, true), (false, false), (true, false), (false, true)]
1240				{
1241					check::<f32>(n, threshold, want, 1e-5);
1242					check::<c32>(n, threshold, want, 1e-5);
1243					check::<f64>(n, threshold, want, 1e-13);
1244					check::<c64>(n, threshold, want, 1e-13);
1245				}
1246			}
1247		}
1248		// the reported `singular_values()` call runs at the default threshold
1249		let threshold = <SvdParams as Auto<f64>>::auto().recursion_threshold;
1250		check::<f64>(256, threshold, (false, false), 1e-13);
1251	}
1252	/// An extreme but finite scale must not overflow or underflow the reduction that
1253	/// bidiagonalisation and the SVD share (#36): the spectrum and the reconstruction
1254	/// stay finite and accurate.
1255	#[test]
1256	fn test_extreme_scale() {
1257		fn check<T: ComplexField>(s: f64, tol: f64) {
1258			let scale = from_f64::<T>(s);
1259			let a = Mat::<T>::from_fn(2, 2, |i, j| match (i, j) {
1260				(0, 0) => scale.copy(),
1261				(0, 1) => scale.mul_real(from_f64::<T::Real>(0.5)),
1262				(1, 1) => scale.copy(),
1263				_ => zero(),
1264			});
1265			let params = Spec::new(SvdParams {
1266				recursion_threshold: 4,
1267				..auto!(T)
1268			});
1269			let mut sigma = Mat::<T>::zeros(2, 2);
1270			let mut u = Mat::<T>::zeros(2, 2);
1271			let mut v = Mat::<T>::zeros(2, 2);
1272			svd(
1273				a.as_ref(),
1274				sigma.as_mut().diagonal_mut(),
1275				Some(u.as_mut()),
1276				Some(v.as_mut()),
1277				Par::Seq,
1278				MemStack::new(&mut MemBuffer::new(svd_scratch::<T>(
1279					2,
1280					2,
1281					ComputeSvdVectors::Thin,
1282					ComputeSvdVectors::Thin,
1283					Par::Seq,
1284					params,
1285				))),
1286				params,
1287			)
1288			.unwrap();
1289			let rec = &u * &sigma * v.adjoint();
1290			let err = (&rec - &a).norm_max();
1291			assert!(rec.is_all_finite());
1292			assert!(err <= a.norm_max() * from_f64::<T::Real>(tol));
1293		}
1294		for s in [1.0f64, 1e160, 1e-300] {
1295			check::<f64>(s, 1e-14);
1296			check::<c64>(s, 1e-14);
1297		}
1298		for s in [1.0f64, 1e30, 1e-35] {
1299			check::<f32>(s, 1e-6);
1300			check::<c32>(s, 1e-6);
1301		}
1302	}
1303	/// A rank-one matrix's bidiagonalisation cascades down to subnormal entries, where the
1304	/// row norm's reciprocal (and, for complex input, the phase normalisation's) overflows.
1305	#[test]
1306	fn test_rank_one_subnormal_cascade() {
1307		for n in [16usize, 32, 150] {
1308			test_svd(Mat::<f32>::full(n, n, 1.0).as_ref());
1309			test_svd(Mat::<c32>::full(n, n, c32::ONE).as_ref());
1310		}
1311		for n in [32usize, 150] {
1312			test_svd(Mat::<f64>::full(n, n, 1.0).as_ref());
1313			test_svd(Mat::<c64>::full(n, n, c64::ONE).as_ref());
1314		}
1315	}
1316	#[test]
1317	fn test_zero_child_at_split() {
1318		for n in [128, 256] {
1319			for pos in [0, n / 2, n - 1] {
1320				let a = Mat::<f64>::from_fn(n, n, |i, j| {
1321					if i == pos && j == pos { 1.0 } else { 0.0 }
1322				});
1323				check_tiny_split(a.as_ref());
1324				let complex =
1325					Mat::<c64>::from_fn(n, n, |i, j| c64::new(0.0, a[(i, j)]));
1326				check_tiny_split(complex.as_ref());
1327			}
1328		}
1329	}
1330	#[test]
1331	fn test_real() {
1332		let rng = &mut StdRng::seed_from_u64(1);
1333		for (m, n) in [
1334			(3, 2),
1335			(2, 2),
1336			(4, 4),
1337			(15, 10),
1338			(10, 10),
1339			(15, 15),
1340			(50, 50),
1341			(100, 100),
1342			(150, 150),
1343			(150, 20),
1344			(20, 150),
1345		] {
1346			let mat = CwiseMatDistribution {
1347				nrows: m,
1348				ncols: n,
1349				dist: StandardNormal,
1350			}
1351			.rand::<Mat<f64>>(rng);
1352			test_svd(mat.as_ref());
1353		}
1354	}
1355	#[test]
1356	fn test_cplx() {
1357		let rng = &mut StdRng::seed_from_u64(1);
1358		for (m, n) in [
1359			(1, 1),
1360			(2, 2),
1361			(3, 2),
1362			(2, 2),
1363			(3, 3),
1364			(4, 4),
1365			(15, 10),
1366			(10, 10),
1367			(15, 15),
1368			(16, 16),
1369			(17, 17),
1370			(18, 18),
1371			(19, 19),
1372			(20, 20),
1373			(30, 30),
1374			(50, 50),
1375			(100, 100),
1376			(150, 150),
1377			(150, 20),
1378			(20, 150),
1379		] {
1380			let mat = CwiseMatDistribution {
1381				nrows: m,
1382				ncols: n,
1383				dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1384			}
1385			.rand::<Mat<c64>>(rng);
1386			test_svd(mat.as_ref());
1387		}
1388	}
1389	#[test]
1390	fn test_special() {
1391		for (m, n) in [
1392			(3, 2),
1393			(2, 2),
1394			(4, 4),
1395			(15, 10),
1396			(10, 10),
1397			(15, 15),
1398			(50, 50),
1399			(100, 100),
1400			(150, 150),
1401			(150, 20),
1402			(20, 150),
1403		] {
1404			test_svd(Mat::<f64>::zeros(m, n).as_ref());
1405			test_svd(Mat::<c64>::zeros(m, n).as_ref());
1406			test_svd(Mat::<f64>::full(m, n, 1.0).as_ref());
1407			test_svd(Mat::<c64>::full(m, n, c64::ONE).as_ref());
1408			test_svd(Mat::<f64>::identity(m, n).as_ref());
1409			test_svd(Mat::<c64>::identity(m, n).as_ref());
1410		}
1411	}
1412	#[test]
1413	fn test_zink() {
1414		let diag = [
1415			-9.931833701529301,
1416			-10.920807536026027,
1417			-52.33647796311243,
1418			2.3685025127736967,
1419			2.421701994236093,
1420			-0.5051763005624579,
1421			-0.04808263896606017,
1422			-0.003875251886338955,
1423			-0.0006413264967716465,
1424			-0.003381944152463707,
1425			2.981152313236375e-5,
1426			5.4290648208388795e-6,
1427			-6.329275972084404e-7,
1428			-6.879142344209158e-7,
1429			-5.265228263479126e-9,
1430			-2.941999902335516e-9,
1431			-1.3060984997930294e-10,
1432			7.07516117218088e-12,
1433			1.8657003929029376e-12,
1434			-6.216080089659131e-14,
1435		];
1436		let subdiag = [
1437			-57.8029649868477,
1438			17.67263066467847,
1439			8.884153814270894,
1440			-9.01998231080713,
1441			-1.028638150814966,
1442			0.22247719217200435,
1443			0.016389886745811315,
1444			-0.004090989452162578,
1445			0.00036818904090536926,
1446			-0.0031394146217732367,
1447			-7.571300829706796e-6,
1448			3.0045718718618155e-6,
1449			2.1329796886727743e-6,
1450			9.259701025627789e-8,
1451			2.2291214755992877e-9,
1452			-2.3017207713252894e-9,
1453			6.807967994979358e-11,
1454			2.1677299575405587e-12,
1455			-3.07282771050034e-13,
1456			0.0,
1457		];
1458		let n = diag.len();
1459		let params = SvdParams {
1460			recursion_threshold: 8,
1461			qr_ratio_threshold: 1.0,
1462			..auto!(f64)
1463		};
1464		let mut d = ColRef::from_slice(&diag).to_owned();
1465		let mut s = ColRef::from_slice(&subdiag).to_owned();
1466		compute_bidiag_real_svd(
1467			d.as_mut().try_as_col_major_mut().unwrap(),
1468			s.as_mut().try_as_col_major_mut().unwrap(),
1469			None,
1470			None,
1471			params,
1472			Par::Seq,
1473			MemStack::new(&mut MemBuffer::new(bidiag_real_svd_scratch::<f64>(
1474				n,
1475				false,
1476				false,
1477				Par::Seq,
1478				params,
1479			))),
1480		)
1481		.unwrap();
1482		assert!(d[n - 1] != 0.0);
1483	}
1484	#[test]
1485	fn test_pinv() {
1486		let rng = &mut StdRng::seed_from_u64(0);
1487		let m = 6;
1488		let n = 36;
1489		let mat = CwiseMatDistribution {
1490			nrows: m,
1491			ncols: n,
1492			dist: StandardNormal,
1493		}
1494		.rand::<Mat<f64>>(rng);
1495		let pinv = mat.svd().unwrap().pseudoinverse();
1496		let err = &mat * &pinv - Mat::<f64>::identity(m, m);
1497		assert!(err.norm_max() < 1e-10);
1498	}
1499	#[test]
1500	fn test_pinv2() {
1501		let rng = &mut StdRng::seed_from_u64(0);
1502		let m = 36;
1503		let n = 6;
1504		let mat = CwiseMatDistribution {
1505			nrows: m,
1506			ncols: n,
1507			dist: StandardNormal,
1508		}
1509		.rand::<Mat<f64>>(rng);
1510		let pinv = mat.svd().unwrap().pseudoinverse();
1511		let err = &pinv * &mat - Mat::<f64>::identity(n, n);
1512		assert!(err.norm_max() < 1e-10);
1513	}
1514	/// The phase reduction's magnitude and phase helpers outside the normal
1515	/// range: for an exact power of two `s`, the singular values of `s B` are
1516	/// `s` times those of `B`. Entries below `min_positive` were flushed to
1517	/// zero or made `inf`/NaN, entries above `1 / min_positive` overflowed.
1518	fn cplx_phase_reduction_is_scale_exact<T: ComplexField>(
1519		make: fn(f64, f64) -> T,
1520	) {
1521		let n = 3;
1522		let base_d = [make(1.8, -0.4), make(-0.3, 0.7), make(0.5, 0.5)];
1523		let base_e = [make(0.2, 0.6), make(-0.8, 0.1)];
1524		let params: SvdParams = auto!(T);
1525		let run = |s: &T::Real| {
1526			let mut d = Col::<T>::from_fn(n, |i| base_d[i].mul_real(s));
1527			let mut e = Col::<T>::from_fn(n, |i| {
1528				if i + 1 < n {
1529					base_e[i].mul_real(s)
1530				} else {
1531					zero()
1532				}
1533			});
1534			let mut u = Mat::<T>::zeros(n + 1, n + 1);
1535			let mut v = Mat::<T>::zeros(n, n);
1536			compute_bidiag_cplx_svd(
1537				d.as_mut().try_as_col_major_mut().unwrap(),
1538				e.as_mut().try_as_col_major_mut().unwrap(),
1539				Some(u.as_mut()),
1540				Some(v.as_mut()),
1541				params,
1542				Par::Seq,
1543				MemStack::new(&mut MemBuffer::new(
1544					bidiag_cplx_svd_scratch::<T>(
1545						n,
1546						true,
1547						true,
1548						Par::Seq,
1549						params,
1550					),
1551				)),
1552			)
1553			.unwrap();
1554			assert!(u.is_all_finite() && v.is_all_finite());
1555			d
1556		};
1557		let reference = run(&one());
1558		let sml = min_positive::<T::Real>();
1559		for s in [sml.recip(), &sml * from_f64::<T::Real>(0.0625)] {
1560			let d = run(&s);
1561			for i in 0..n {
1562				let scaled_back = d[i].real() / &s;
1563				assert!(
1564					(scaled_back - reference[i].real()).abs()
1565						<= from_f64::<T::Real>(1e3)
1566							* eps::<T::Real>() * reference[0].real()
1567				);
1568			}
1569		}
1570	}
1571	#[test]
1572	fn cplx_phase_reduction_outside_normal_range() {
1573		cplx_phase_reduction_is_scale_exact::<c64>(c64::new);
1574		cplx_phase_reduction_is_scale_exact::<c32>(|r, i| {
1575			c32::new(r as f32, i as f32)
1576		});
1577		cplx_phase_reduction_is_scale_exact::<f64>(|r, _| r);
1578		cplx_phase_reduction_is_scale_exact::<f32>(|r, _| r as f32);
1579	}
1580	/// full factors must be orthogonal even when the caller's output buffers
1581	/// hold old entries; the tall/wide QR path used to leave the top-right block
1582	/// of `U` (and, after transposition, of `V`) as it found it
1583	#[test]
1584	fn full_factors_ignore_old_output_entries() {
1585		for (m, n) in [(4, 2), (2, 4)] {
1586			let a = Mat::<f64>::identity(m, n);
1587			let size = Ord::min(m, n);
1588			let mut s = Diag::<f64>::zeros(size);
1589			let mut u = Mat::<f64>::full(m, m, 1.0);
1590			let mut v = Mat::<f64>::full(n, n, 1.0);
1591			svd(
1592				a.as_ref(),
1593				s.as_mut(),
1594				Some(u.as_mut()),
1595				Some(v.as_mut()),
1596				Par::Seq,
1597				MemStack::new(&mut MemBuffer::new(svd_scratch::<f64>(
1598					m,
1599					n,
1600					ComputeSvdVectors::Full,
1601					ComputeSvdVectors::Full,
1602					Par::Seq,
1603					default(),
1604				))),
1605				default(),
1606			)
1607			.unwrap();
1608			let orth_u =
1609				(u.transpose() * &u - Mat::<f64>::identity(m, m)).norm_max();
1610			let orth_v =
1611				(v.transpose() * &v - Mat::<f64>::identity(n, n)).norm_max();
1612			assert!(orth_u < 1e-12, "U orthogonality error {orth_u}");
1613			assert!(orth_v < 1e-12, "V orthogonality error {orth_v}");
1614			let rec = u.as_ref().get(.., ..size) * s.as_ref()
1615				* v.as_ref().get(.., ..size).transpose();
1616			assert!((&rec - &a).norm_max() < 1e-12);
1617		}
1618	}
1619}