Skip to content

Commit 0ecccbf

Browse files
add exceptional shift (#1603)
* run cargo fmt * add comment on exceptional shift * Update src/linalg/schur.rs Co-authored-by: Alex H. Room <69592136+alexhroom@users.noreply.github.com> --------- Co-authored-by: Alex H. Room <69592136+alexhroom@users.noreply.github.com>
1 parent 5f927f6 commit 0ecccbf

2 files changed

Lines changed: 66 additions & 4 deletions

File tree

src/linalg/schur.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ where
135135

136136
// Implicit double-shift QR method.
137137
let mut niter = 0;
138+
// Number of QR steps since the active window last shrank due to a deflation.
139+
let mut kdefl: usize = 0;
140+
// Apply Wilkinson's exceptional shift every kexsh stalled QR steps.
141+
let kexsh: usize = 10;
142+
// This is used by the exceptional shift algorithm as mentioned in
143+
// Wilkinson, J. H., & Reinsch, C. (1971).
144+
// Handbook for automatic computation (Vol. 2: Linear algebra)
145+
// P.373
146+
let exceptional_shift_a: T::RealField = crate::convert(0.75);
147+
let exceptional_shift_b: T::RealField = crate::convert(-0.4375);
138148
let (mut start, mut end) = Self::delimit_subproblem(&mut t, eps.clone(), dim.value() - 1);
139149

140150
while end != start {
@@ -150,10 +160,28 @@ where
150160
let h22 = t[(start + 1, start + 1)].clone();
151161
let h32 = t[(start + 2, start + 1)].clone();
152162

153-
let hnn = t[(n, n)].clone();
154-
let hmm = t[(m, m)].clone();
155-
let hnm = t[(n, m)].clone();
156-
let hmn = t[(m, n)].clone();
163+
let (hnn, hmm, hnm, hmn) = if kdefl != 0 && kdefl % kexsh == 0 {
164+
let s = t[(start + 1, start)].clone().norm1()
165+
+ t[(start + 2, start + 1)].clone().norm1();
166+
let h = t[(start, start)].clone()
167+
+ T::from_real(exceptional_shift_a.clone() * s.clone());
168+
// When the implicit QR step stalls, replace the trailing 2x2 Wilkinson shift
169+
// with the exceptional-shift coefficients from Wilkinson & Reinsch. This
170+
// perturbs the bulge start enough to escape the non-convergent cycle.
171+
(
172+
h.clone(),
173+
h,
174+
T::from_real(exceptional_shift_b.clone() * s.clone()),
175+
T::from_real(s),
176+
)
177+
} else {
178+
(
179+
t[(n, n)].clone(),
180+
t[(m, m)].clone(),
181+
t[(n, m)].clone(),
182+
t[(m, n)].clone(),
183+
)
184+
};
157185

158186
let tra = hnn.clone() + hmm.clone();
159187
let det = hnn * hmm - hnm * hmn;
@@ -256,10 +284,19 @@ where
256284
}
257285
}
258286

287+
let prev_start = start;
288+
let prev_end = end;
259289
let sub = Self::delimit_subproblem(&mut t, eps.clone(), end);
260290

261291
start = sub.0;
262292
end = sub.1;
293+
if end < prev_end || start > prev_start {
294+
// A split or deflation changed the active window, so restart the stall counter.
295+
kdefl = 0;
296+
} else {
297+
// No deflation occurred: count another QR step toward an exceptional shift.
298+
kdefl += 1;
299+
}
263300

264301
niter += 1;
265302
if niter == max_niter {

tests/linalg/eigen.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,31 @@ fn eigenvalues_search_should_not_hang_issue_1528() {
170170
);
171171
}
172172

173+
// Regression test for #611
174+
#[test]
175+
#[rustfmt::skip]
176+
fn eigenvalues_search_should_not_hang_issue_611() {
177+
let m = nalgebra::Matrix4::<f64>::new(
178+
0.0, 0.0, 0.0, -0.8286,
179+
1.0, 0.0, 0.0, 0.0,
180+
0.0, 1.0, 0.0, 1.7094,
181+
0.0, 0.0, 1.0, 0.0,
182+
);
183+
let complex_eigenvals = m.complex_eigenvalues();
184+
185+
assert_relative_eq!(
186+
complex_eigenvals.iter().sum::<Complex<f64>>().re,
187+
m.trace(),
188+
epsilon = 1e-10
189+
);
190+
191+
assert_relative_eq!(
192+
complex_eigenvals.iter().product::<Complex<f64>>().re,
193+
m.determinant(),
194+
epsilon = 1e-10
195+
);
196+
}
197+
173198
// #[cfg(feature = "arbitrary")]
174199
// quickcheck! {
175200
// TODO: full eigendecomposition is not implemented yet because of its complexity when some

0 commit comments

Comments
 (0)