Skip to main content

faer/linalg/evd/
balance.rs

1use super::*;
2use crate::assert;
3use crate::perm::{swap_cols_idx, swap_rows_idx};
4
5/// Balancing failed because the input contains a nonfinite entry.
6///
7/// # Examples
8/// ```
9/// use dyn_stack::{MemBuffer, MemStack};
10/// use faer::Par;
11/// use faer::linalg::evd::*;
12/// let mut a = faer::mat![[f64::NAN]];
13/// let mut mem = MemBuffer::new(balance_scratch());
14/// assert_eq!(
15/// 	balance_in_place(
16/// 		a.as_mut(),
17/// 		&mut [0],
18/// 		&mut [1.0],
19/// 		Par::Seq,
20/// 		MemStack::new(&mut mem)
21/// 	),
22/// 	Err(BalanceError::NonFiniteInput)
23/// );
24/// ```
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26pub enum BalanceError {
27	/// NaN or infinity in the input; the matrix is unchanged.
28	NonFiniteInput,
29}
30
31/// Workspace for [`balance_in_place`]; all metadata is supplied by the caller.
32///
33/// # Examples
34/// ```
35/// assert_eq!(faer::linalg::evd::balance_scratch().size_bytes(), 0);
36/// ```
37pub fn balance_scratch() -> StackReq {
38	StackReq::EMPTY
39}
40
41/// Permutes and balances a square matrix in place, like `xGEBAL(job=B)`.
42/// Returns the zero-based, half-open active range `ilo..ihi`. Outside this
43/// range the permutation isolates eigenvalues in triangular blocks.
44///
45/// On return `b[i,j] = a[perm[i],perm[j]] * scale[j] / scale[i]`:
46/// `perm[i]` is the original index now at row/column `i`, and `scale` holds
47/// positive powers of two (one outside the active range). Scaling is accepted
48/// only if it reduces the sum of the active row/column 2-norms (including the
49/// diagonal) by at least 5%, subject to safe-range guards. This is the LAPACK
50/// 3.5+ criterion, rather than the older 1-norm rule.
51///
52/// Use [`balance_eigenvectors_in_place`] for right eigenvectors. For a matrix
53/// function such as `exp`, undo by `f(a)[perm[i],perm[j]] =
54/// scale[i] * f(b)[i,j] / scale[j]`. Powers of two introduce no rounding when
55/// the scaled values stay normal; underflow can prevent bit-for-bit recovery.
56///
57/// # Panics
58/// Panics unless `a` is square and both metadata slices have length `n`.
59///
60/// # Errors
61/// Returns [`BalanceError::NonFiniteInput`] before modifying any output if an
62/// entry is NaN or infinite.
63///
64/// # Examples
65/// ```
66/// use dyn_stack::{MemBuffer, MemStack};
67/// use faer::Par;
68/// use faer::linalg::evd::*;
69/// let mut a = faer::mat![[1.0f64, 1024.0], [1.0 / 1024.0, 2.0]];
70/// let (mut perm, mut scale) = ([0; 2], [0.0; 2]);
71/// let mut mem = MemBuffer::new(balance_scratch());
72/// let active = balance_in_place(
73/// 	a.as_mut(),
74/// 	&mut perm,
75/// 	&mut scale,
76/// 	Par::Seq,
77/// 	MemStack::new(&mut mem),
78/// )
79/// .unwrap();
80/// assert_eq!(active, 0..2);
81/// let row = a.row(0).norm_l2();
82/// let col = a.col(0).norm_l2();
83/// assert!(row <= 2.0 * col && col <= 2.0 * row);
84/// assert_eq!(a[(0, 1)] * scale[0] / scale[1], 1024.0);
85/// ```
86pub fn balance_in_place<T: ComplexField>(
87	mut a: MatMut<'_, T>,
88	perm: &mut [usize],
89	scale: &mut [T::Real],
90	_par: Par,
91	_stack: &mut MemStack,
92) -> Result<core::ops::Range<usize>, BalanceError> {
93	let n = a.nrows();
94	assert!(a.ncols() == n && perm.len() == n && scale.len() == n);
95	if !a.is_all_finite() {
96		return Err(BalanceError::NonFiniteInput);
97	}
98	for i in 0..n {
99		perm[i] = i;
100		scale[i] = one();
101	}
102	let mut lo = 0;
103	let mut hi = n;
104	loop {
105		let row = (lo..hi)
106			.rev()
107			.find(|&i| (lo..hi).all(|j| i == j || a[(i, j)] == zero()));
108		if let Some(i) = row {
109			hi -= 1;
110			swap_rows_idx(a.rb_mut(), i, hi);
111			swap_cols_idx(a.rb_mut(), i, hi);
112			perm.swap(i, hi);
113			continue;
114		}
115		let col =
116			(lo..hi).find(|&j| (lo..hi).all(|i| i == j || a[(i, j)] == zero()));
117		if let Some(j) = col {
118			swap_rows_idx(a.rb_mut(), j, lo);
119			swap_cols_idx(a.rb_mut(), j, lo);
120			perm.swap(j, lo);
121			lo += 1;
122			continue;
123		}
124		break;
125	}
126	// Own implementation of Parlett-Reinsch balancing (Numer. Math. 13,
127	// 293-304, 1969), using the 2-norm convergence rule of LAPACK >= 3.5
128	// xGEBAL (James-Langou-Lowery, arXiv:1401.5766). No LAPACK source is
129	// copied.
130	let two = from_f64::<T::Real>(2.0);
131	let half = from_f64::<T::Real>(0.5);
132	let factor = from_f64::<T::Real>(0.95);
133	let small = min_positive::<T::Real>() / eps::<T::Real>();
134	let large = small.recip();
135	loop {
136		let mut changed = false;
137		for i in lo..hi {
138			let mut c = zero::<T::Real>();
139			let mut r = zero::<T::Real>();
140			for j in lo..hi {
141				c = c.hypot(&a[(j, i)].abs());
142				r = r.hypot(&a[(i, j)].abs());
143			}
144			if c == zero() || r == zero() || !c.is_finite() || !r.is_finite() {
145				continue;
146			}
147			let mut ca = zero::<T::Real>();
148			let mut ra = zero::<T::Real>();
149			for j in 0..n {
150				ca = ca.fmax(a[(j, i)].abs());
151				ra = ra.fmax(a[(i, j)].abs());
152			}
153			let original = c.mul_pow2(half.copy()) + r.mul_pow2(half.copy());
154			let mut f = one::<T::Real>();
155			while c < r.mul_pow2(half.copy())
156				&& c < large.mul_pow2(half.copy())
157				&& ca < large.mul_pow2(half.copy())
158				&& r > &small * &two
159				&& ra > &small * &two
160				&& f < large.mul_pow2(half.copy())
161			{
162				c *= &two;
163				r *= &half;
164				ca *= &two;
165				ra *= &half;
166				f *= &two;
167			}
168			while r <= c.mul_pow2(half.copy())
169				&& r < large.mul_pow2(half.copy())
170				&& ra < large.mul_pow2(half.copy())
171				&& c > &small * &two
172				&& ca > &small * &two
173				&& f > &small * &two
174			{
175				c *= &half;
176				r *= &two;
177				ca *= &half;
178				ra *= &two;
179				f *= &half;
180			}
181			if c.mul_pow2(half.copy()) + r.mul_pow2(half.copy())
182				>= &factor * &original
183			{
184				continue;
185			}
186			if (f < one() && scale[i] < &small / &f)
187				|| (f > one() && scale[i] > &large / &f)
188			{
189				continue;
190			}
191			scale[i] *= &f;
192			let inv = f.recip();
193			for j in 0..n {
194				if j != i {
195					a[(i, j)] = a[(i, j)].mul_pow2(inv.copy());
196					a[(j, i)] = a[(j, i)].mul_pow2(f.copy());
197				}
198			}
199			changed = true;
200		}
201		if !changed {
202			break;
203		}
204	}
205	Ok(lo..hi)
206}
207
208/// Workspace for [`balance_eigenvectors_in_place`].
209///
210/// # Examples
211/// ```
212/// let _buffer = dyn_stack::MemBuffer::new(
213/// 	faer::linalg::evd::balance_eigenvectors_scratch::<f64>(3),
214/// );
215/// ```
216pub fn balance_eigenvectors_scratch<T: ComplexField>(dim: usize) -> StackReq {
217	temp_mat_scratch::<T>(dim, 1)
218}
219
220/// Back-transforms columns of right eigenvectors: `x_original = P D
221/// x_balanced`. Accepts any number of columns, including the real/imaginary
222/// pair encoding. The columns are not renormalized. `perm` and `scale` are the
223/// metadata from [`balance_in_place`].
224///
225/// # Panics
226/// Panics on incompatible lengths, a non-permutation, a nonpositive/nonfinite
227/// scale, or insufficient scratch. Metadata must describe the balanced input.
228///
229/// # Examples
230/// ```
231/// use dyn_stack::{MemBuffer, MemStack};
232/// use faer::Par;
233/// use faer::linalg::evd::*;
234/// let mut x = faer::mat![[1.0f64], [2.0]];
235/// let mut mem = MemBuffer::new(balance_eigenvectors_scratch::<f64>(2));
236/// balance_eigenvectors_in_place(
237/// 	x.as_mut(),
238/// 	&[1, 0],
239/// 	&[2.0, 0.5],
240/// 	Par::Seq,
241/// 	MemStack::new(&mut mem),
242/// );
243/// assert_eq!(x, faer::mat![[1.0], [2.0]]);
244/// ```
245pub fn balance_eigenvectors_in_place<T: ComplexField>(
246	mut x: MatMut<'_, T>,
247	perm: &[usize],
248	scale: &[T::Real],
249	_par: Par,
250	stack: &mut MemStack,
251) {
252	let n = x.nrows();
253	assert!(perm.len() == n && scale.len() == n);
254	let (mut tmp, _) = temp_mat_zeroed::<T, _, _>(n, 1, stack);
255	let mut tmp = tmp.as_mat_mut().col_mut(0);
256	// Use the output-sized workspace to validate the permutation once.
257	for &i in perm {
258		assert!(i < n && tmp[i] == zero());
259		tmp[i] = one();
260	}
261	assert!(scale.iter().all(|s| s.is_finite() && *s > zero()));
262	for j in 0..x.ncols() {
263		for i in 0..n {
264			tmp[perm[i]] = x[(i, j)].mul_pow2(scale[i].copy());
265		}
266		x.rb_mut().col_mut(j).copy_from(tmp.rb());
267	}
268}