1use crate::assert;
2use crate::internal_prelude::*;
3use crate::utils::cost::{ParThreshold, rate};
4use crate::utils::thread::with_width;
5use linalg::householder;
6use linalg::matmul::triangular::BlockStructure;
7use linalg::matmul::{self, dot};
8#[derive(Copy, Clone, Debug)]
10pub struct TridiagParams {
11 pub par_threshold: ParThreshold,
15 #[doc(hidden)]
16 pub non_exhaustive: NonExhaustive,
17}
18impl<T: ComplexField> Auto<T> for TridiagParams {
19 fn auto() -> Self {
20 Self {
21 par_threshold: ParThreshold::Derived,
22 non_exhaustive: NonExhaustive(()),
23 }
24 }
25}
26pub fn tridiag_in_place_scratch<T: ComplexField>(
29 dim: usize,
30 par: ParShape,
31 params: Spec<TridiagParams, T>,
32) -> StackReq {
33 _ = par;
34 _ = params;
35 StackReq::all_of(&[
36 temp_mat_scratch::<T>(dim, 1).array(2),
37 temp_mat_scratch::<T>(dim, par.degree()),
38 ])
39}
40fn tridiag_fused_op_simd<T: ComplexField>(
41 A: MatMut<'_, T, usize, usize, ContiguousFwd>,
42 y2: ColMut<'_, T, usize>,
43 z2: ColMut<'_, T, usize, ContiguousFwd>,
44 ry2: ColRef<'_, T, usize>,
45 rz2: ColRef<'_, T, usize, ContiguousFwd>,
46 u0: ColRef<'_, T, usize, ContiguousFwd>,
47 u1: ColRef<'_, T, usize>,
48 u2: ColRef<'_, T, usize>,
49 v2: ColRef<'_, T, usize, ContiguousFwd>,
50 f: T,
51 align: usize,
52) {
53 struct Impl<'a, 'M, 'N, T: ComplexField> {
54 A: MatMut<'a, T, Dim<'M>, Dim<'N>, ContiguousFwd>,
55 y2: ColMut<'a, T, Dim<'N>>,
56 z2: ColMut<'a, T, Dim<'M>, ContiguousFwd>,
57 ry2: ColRef<'a, T, Dim<'N>>,
58 rz2: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
59 u0: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
60 u1: ColRef<'a, T, Dim<'N>>,
61 u2: ColRef<'a, T, Dim<'N>>,
62 v2: ColRef<'a, T, Dim<'M>, ContiguousFwd>,
63 f: T,
64 align: usize,
65 }
66 impl<'a, 'M, 'N, T: ComplexField> pulp::WithSimd for Impl<'a, 'M, 'N, T> {
67 type Output = ();
68
69 #[inline(always)]
70 fn with_simd<S: pulp::Simd>(self, simd: S) -> Self::Output {
71 let Self {
72 mut A,
73 mut y2,
74 mut z2,
75 ry2,
76 rz2,
77 u0,
78 u1,
79 u2,
80 v2,
81 ref f,
82 mut align,
83 } = self;
84 let simd = T::simd_ctx(simd);
85 let (m, n) = A.shape();
86 {
87 let simd = SimdCtx::<T, S>::new_align(simd, m, align);
88 simd_iter!(for i in [simd.indices()] {
89 simd.write(z2.rb_mut(), i, simd.zero());
90 });
91 }
92 for j in n.indices() {
93 let i = m.idx_inc(*j);
94 with_dim!(m, *m - *j);
95 let simd = SimdCtx::<T, S>::new_align(simd, m, align);
96 align -= 1;
97 let mut A = A.rb_mut().col_mut(j).subrows_mut(i, m);
98 let mut z = z2.rb_mut().subrows_mut(i, m);
99 let rz = rz2.subrows(i, m);
100 let ua = u0.subrows(i, m);
101 let v = v2.subrows(i, m);
102 let y = y2.rb_mut().at_mut(j);
103 let ry = simd.splat(&(-&ry2[j]));
104 let ub = simd.splat(&(-&u1[j]));
105 let uc = simd.splat(&(f * &u2[j]));
106 let mut acc = [simd.zero(); 4];
107 simd_iter!(for (IDX, i) in [simd.batch_indices(); 4] {
108 let mut a = simd.read(A.rb(), i);
109 a = simd.conj_mul_add(ry, simd.read(ua, i), a);
110 a = simd.conj_mul_add(ub, simd.read(rz, i), a);
111 simd.write(A.rb_mut(), i, a);
112 let tmp = simd.read(z.rb(), i);
113 simd.write(z.rb_mut(), i, simd.mul_add(a, uc, tmp));
114 acc[IDX] = simd.conj_mul_add(a, simd.read(v, i), acc[IDX]);
115 });
116 let acc0 = simd.add(acc[0], acc[1]);
117 let acc2 = simd.add(acc[2], acc[3]);
118 let acc0 = simd.add(acc0, acc2);
119 let acc0 = simd.reduce_sum(acc0);
120 let i0 = m.idx(0);
121 *y = f * (acc0 - &A[i0] * &v[i0]);
122 }
123 }
124 }
125 with_dim!(M, A.nrows());
126 with_dim!(N, A.ncols());
127 dispatch!(
128 Impl {
129 A: A.as_shape_mut(M, N),
130 y2: y2.as_row_shape_mut(N),
131 z2: z2.as_row_shape_mut(M),
132 ry2: ry2.as_row_shape(N),
133 rz2: rz2.as_row_shape(M),
134 u0: u0.as_row_shape(M),
135 u1: u1.as_row_shape(N),
136 u2: u2.as_row_shape(N),
137 v2: v2.as_row_shape(M),
138 f,
139 align,
140 },
141 Impl,
142 T
143 )
144}
145fn tridiag_fused_op<T: ComplexField>(
146 A: MatMut<'_, T>,
147 y2: ColMut<'_, T>,
148 z2: ColMut<'_, T>,
149 ry2: ColRef<'_, T>,
150 rz2: ColRef<'_, T>,
151 u0: ColRef<'_, T>,
152 u1: ColRef<'_, T>,
153 u2: ColRef<'_, T>,
154 v2: ColRef<'_, T>,
155 f: T,
156 align: usize,
157) {
158 let mut A = A;
159 let mut z2 = z2;
160 if const { T::SIMD_CAPABILITIES.is_simd() } {
161 if let (Some(A), Some(z2), Some(rz2), Some(u0), Some(v2)) = (
162 A.rb_mut().try_as_col_major_mut(),
163 z2.rb_mut().try_as_col_major_mut(),
164 rz2.try_as_col_major(),
165 u0.try_as_col_major(),
166 v2.try_as_col_major(),
167 ) {
168 tridiag_fused_op_simd(
169 A, y2, z2, ry2, rz2, u0, u1, u2, v2, f, align,
170 );
171 } else {
172 tridiag_fused_op_fallback(A, y2, z2, ry2, rz2, u0, u1, u2, v2, f);
173 }
174 } else {
175 tridiag_fused_op_fallback(A, y2, z2, ry2, rz2, u0, u1, u2, v2, f);
176 }
177}
178fn tridiag_fused_op_fallback<T: ComplexField>(
179 A: MatMut<'_, T>,
180 y2: ColMut<'_, T>,
181 z2: ColMut<'_, T>,
182 ry2: ColRef<'_, T>,
183 rz2: ColRef<'_, T>,
184 u0: ColRef<'_, T>,
185 u1: ColRef<'_, T>,
186 u2: ColRef<'_, T>,
187 v2: ColRef<'_, T>,
188 f: T,
189) {
190 let mut par = Par::Seq;
191 let mut A = A;
192 let mut y2 = y2;
193 let n = A.ncols();
194 let (mut A0, mut A1) = A.rb_mut().split_at_row_mut(n);
195 let (u00, u01) = u0.split_at_row(n);
196 let (v20, v21) = v2.split_at_row(n);
197 let (mut z20, mut z21) = z2.split_at_row_mut(n);
198 let (rz20, rz21) = rz2.split_at_row(n);
199 matmul::triangular::matmul(
200 A0.rb_mut(),
201 BlockStructure::TriangularLower,
202 Accum::Add,
203 u00,
204 BlockStructure::Rectangular,
205 ry2.adjoint(),
206 BlockStructure::Rectangular,
207 -one::<T>(),
208 par.reborrow(),
209 );
210 matmul::triangular::matmul(
211 A0.rb_mut(),
212 BlockStructure::TriangularLower,
213 Accum::Add,
214 rz20,
215 BlockStructure::Rectangular,
216 u1.adjoint(),
217 BlockStructure::Rectangular,
218 -one::<T>(),
219 par.reborrow(),
220 );
221 matmul::matmul(
222 A1.rb_mut(),
223 Accum::Add,
224 u01,
225 ry2.adjoint(),
226 -one::<T>(),
227 par.reborrow(),
228 );
229 matmul::matmul(
230 A1.rb_mut(),
231 Accum::Add,
232 rz21,
233 u1.adjoint(),
234 -one::<T>(),
235 par.reborrow(),
236 );
237 matmul::triangular::matmul(
238 z20.rb_mut(),
239 BlockStructure::Rectangular,
240 Accum::Replace,
241 A0.rb(),
242 BlockStructure::TriangularLower,
243 u2,
244 BlockStructure::Rectangular,
245 f.clone(),
246 par.reborrow(),
247 );
248 matmul::triangular::matmul(
249 y2.rb_mut(),
250 BlockStructure::Rectangular,
251 Accum::Replace,
252 A0.rb().adjoint(),
253 BlockStructure::StrictTriangularUpper,
254 v20,
255 BlockStructure::Rectangular,
256 f.clone(),
257 par.reborrow(),
258 );
259 matmul::matmul(z21.rb_mut(), Accum::Replace, A1.rb(), u2, f.clone(), par.reborrow());
260 matmul::matmul(
261 y2.rb_mut(),
262 Accum::Add,
263 A1.rb().adjoint(),
264 v21,
265 f.clone(),
266 par,
267 );
268}
269pub fn tridiag_in_place<T: ComplexField>(
279 A: MatMut<'_, T>,
280 householder: MatMut<'_, T>,
281 par: Par,
282 stack: &mut MemStack,
283 params: Spec<TridiagParams, T>,
284) {
285 let params = params.config;
286 let mut A = A;
287 let mut H = householder;
288 let mut par = par;
289 if matches!(params.par_threshold, ParThreshold::Never) {
291 par = Par::Seq;
292 }
293 let n = A.nrows();
294 let b = H.nrows();
295 assert!(H.ncols() == n.saturating_sub(1));
296 if n == 0 {
297 return;
298 }
299 alloca!('stack: {
300 let mut y = unsafe { col![uninit::<T>, n] };
301 let mut w = unsafe { col![uninit::<T>, n] };
302 let mut z = unsafe { mat![uninit::<T>, n, par.degree()] };
303 });
304 {
305 let mut H = H.rb_mut().row_mut(0);
306 for k in 0..n {
307 let (_, A01, A10, A11) = A.rb_mut().split_at_mut(k, k);
308 let (_, _) = A01.split_first_col().unwrap();
309 let (_, A20) = A10.split_first_row_mut().unwrap();
310 let (mut A11, _, A21, mut A22) = A11.split_at_mut(1, 1);
311 let mut A21 = A21.col_mut(0);
312 let a11 = &mut A11[(0, 0)];
313 let (y1, mut y2) =
314 y.rb_mut().split_at_row_mut(k).1.split_at_row_mut(1);
315 if k > 0 {
316 let y1 = y1[0].copy();
317 let p = k - 1;
318 let u2 = A20.rb().col(p);
319 *a11 -= &y1 + y1.conj();
320 z!(A21.rb_mut(), u2, y2.rb()).for_each(
321 |uz!(a, u, y): Zip!(&mut T, &T, &T)| {
322 *a -= y1.conj() * u + y;
323 },
324 );
325 }
326 if k + 1 == n {
327 break;
328 }
329 let rem = n - k - 1;
330 let width = params.par_threshold.region_width::<T>(&par, rem * rem / 2, rate::tridiag(), 1);
333 let k1 = k + 1;
334 let tau_inv;
335 {
336 let (mut a11, mut x2) = A21.rb_mut().split_at_row_mut(1);
337 let a11 = &mut a11[0];
338 let householder::HouseholderInfo { tau, .. } =
339 householder::make_householder_in_place(a11, x2.rb_mut());
340 tau_inv = tau.real().recip();
341 H[k] = tau.to_cplx();
342 let mut z2 = z.rb_mut().split_at_row_mut(k + 2).1;
343 let mut w2 = w.rb_mut().split_at_row_mut(k + 2).1;
344 let (mut y1, mut y2) = y2.rb_mut().split_at_row_mut(1);
345 let y1 = &mut y1[0];
346 let (A1, A2) = A22.rb_mut().split_at_row_mut(1);
347 let A1 = A1.row_mut(0);
348 let (mut a11, _) = A1.split_at_col_mut(1);
349 let a11 = &mut a11[0];
350 let (A21, mut A22) = A2.split_at_col_mut(1);
351 let mut A21 = A21.col_mut(0);
352 if k > 0 {
353 let p = k - 1;
354 let (u1, u2) = A20.rb().col(p).split_at_row(1);
355 let ref u1 = u1[0].copy();
356 *a11 -= u1 * y1.conj() + &*y1 * u1.conj();
357 z!(A21.rb_mut(), u2.rb(), y2.rb()).for_each(
358 |uz!(a, u, y): Zip!(&mut T, &T, &T)| {
359 *a -= u * y1.conj() + &*y * u1.conj();
360 },
361 );
362 w2.copy_from(y2.rb());
363 with_width(par.reborrow(), width, |par| {
364 match par {
365 Par::Seq => {
366 let mut z2 = z2.rb_mut().col_mut(0);
367 tridiag_fused_op(
368 A22.rb_mut(),
369 y2.rb_mut(),
370 z2.rb_mut(),
371 w2.rb(),
372 w2.rb(),
373 u2.rb(),
374 u2.rb(),
375 x2.rb(),
376 x2.rb(),
377 tau_inv.to_cplx(),
378 simd_align(k1 + 1),
379 );
380 z!(y2.rb_mut(), z2.rb()).for_each(
381 |uz!(y, z): Zip!(&mut T, &T)| *y += z,
382 );
383 },
384 #[cfg(feature = "rayon")]
385 _ => {
386 let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
387 use rayon::prelude::*;
388 let nthreads = nthreads.get();
389 let mut z2 = z2.rb_mut().subcols_mut(0, nthreads);
390 let n2 = A22.ncols();
391 assert!((n2 as u64) < (1u64 << 50));
392 let idx_to_col_start = |idx: usize| {
393 let idx_as_percent =
394 idx as f64 / nthreads as f64;
395 let col_start_percent = 1.0f64
396 - libm::sqrt(1.0f64 - idx_as_percent);
397 (col_start_percent * n2 as f64) as usize
398 };
399 {
400 let A22 = A22.rb();
401 let y2 = y2.rb();
402 let ref f = tau_inv.to_cplx::<T>();
403 crate::utils::thread::for_each(par,
404 nthreads,
405 z2.rb_mut().par_col_iter_mut().enumerate(),
406 |(idx, mut z2)| {
407 let first = idx_to_col_start(idx);
408 let last_col =
409 idx_to_col_start(idx + 1);
410 let nrows = n2 - first;
411 let ncols = last_col - first;
412 let mut A = unsafe {
413 A22.rb()
414 .subcols(first, ncols)
415 .subrows(first, nrows)
416 .const_cast()
417 };
418 {
419 let y2 = unsafe {
420 y2.subrows(first, ncols)
421 .const_cast()
422 };
423 let mut z2 = z2
424 .rb_mut()
425 .subrows_mut(first, nrows);
426 let ry2 =
427 w2.rb().subrows(first, ncols);
428 let rz2 =
429 w2.rb().subrows(first, nrows);
430 let u0 = u2.subrows(first, nrows);
431 let u1 = u2.subrows(first, ncols);
432 let u2 =
433 x2.rb().subrows(first, ncols);
434 let v2 =
435 x2.rb().subrows(first, nrows);
436 tridiag_fused_op(
437 A.rb_mut(),
438 y2,
439 z2.rb_mut(),
440 ry2,
441 rz2,
442 u0,
443 u1,
444 u2,
445 v2,
446 f.copy(),
447 n.next_power_of_two()
448 - (k1 + 1) - first,
449 );
450 }
451 z2.rb_mut()
452 .subrows_mut(0, first)
453 .fill(zero());
454 },
455 );
456 }
457 for z2 in z2.rb().col_iter() {
458 z!(y2.rb_mut(), z2.rb())
459 .for_each(|uz!(y, z)| *y += z);
460 }
461 },
462 }
463 });
464 } else {
465 with_width(par.reborrow(), width, |par| {
466 matmul::triangular::matmul(
467 y2.rb_mut(),
468 BlockStructure::Rectangular,
469 Accum::Replace,
470 A22.rb(),
471 BlockStructure::TriangularLower,
472 x2.rb(),
473 BlockStructure::Rectangular,
474 tau_inv.to_cplx(),
475 par,
476 );
477 });
478 with_width(par.reborrow(), width, |par| {
479 matmul::triangular::matmul(
480 y2.rb_mut(),
481 BlockStructure::Rectangular,
482 Accum::Add,
483 A22.rb().adjoint(),
484 BlockStructure::StrictTriangularUpper,
485 x2.rb(),
486 BlockStructure::Rectangular,
487 tau_inv.to_cplx(),
488 par,
489 );
490 });
491 }
492 z!(y2.rb_mut(), A21.rb()).for_each(
493 |uz!(y, a): Zip!(&mut T, &T)| {
494 *y += a.mul_real(&tau_inv);
495 },
496 );
497 *y1 = (&*a11
498 + dot::inner_prod(
499 A21.rb().transpose(),
500 Conj::Yes,
501 x2.rb(),
502 Conj::No,
503 ))
504 .mul_real(&tau_inv);
505 let ref b = (&*y1
506 + dot::inner_prod(
507 x2.rb().transpose(),
508 Conj::Yes,
509 y2.rb(),
510 Conj::No,
511 ))
512 .mul_pow2(from_f64::<T::Real>(0.5))
513 .mul_real(&tau_inv);
514 *y1 -= b;
515 z!(y2.rb_mut(), x2.rb()).for_each(
516 |uz!(y, u): Zip!(&mut T, &T)| {
517 *y -= b * u;
518 },
519 );
520 }
521 }
522 }
523 if n > 0 {
524 let n = n - 1;
525 let A = A.rb().submatrix(1, 0, n, n);
526 let mut H = H.rb_mut().subcols_mut(0, n);
527 let mut j = 0;
528 while j < n {
529 let b = Ord::min(b, n - j);
530 let mut H = H.rb_mut().submatrix_mut(0, j, b, b);
531 for k in 0..b {
532 H[(k, k)] = H[(0, k)].copy();
533 }
534 householder::upgrade_householder_factor(
535 H.rb_mut(),
536 A.submatrix(j, j, n - j, b),
537 b,
538 1,
539 par.reborrow(),
540 );
541 j += b;
542 }
543 }
544}
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use crate::stats::prelude::*;
549 use crate::utils::approx::*;
550 use crate::{Mat, assert, c64};
551 use dyn_stack::MemBuffer;
552 #[test]
553 fn test_tridiag_real() {
554 let rng = &mut StdRng::seed_from_u64(0);
555 for n in [2, 3, 4, 8, 16] {
556 let A = CwiseMatDistribution {
557 nrows: n,
558 ncols: n,
559 dist: StandardNormal,
560 }
561 .rand::<Mat<f64>>(rng);
562 let A = A.rb() + A.adjoint();
563 let b = 3;
564 let mut H = Mat::zeros(b, n - 1);
565 let mut V = A.clone();
566 let mut V = V.as_mut();
567 tridiag_in_place(
568 V.rb_mut(),
569 H.rb_mut(),
570 Par::Seq,
571 MemStack::new(&mut MemBuffer::new(StackReq::all_of(&[
572 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n - 1, b, n),
573 tridiag_in_place_scratch::<f64>(n, ParShape::Seq, default()),
574 ]))),
575 default(),
576 );
577 let mut A = A.clone();
578 let mut A = A.as_mut();
579 for iter in 0..2 {
580 let mut A = if iter == 0 {
581 A.rb_mut()
582 } else {
583 A.rb_mut().transpose_mut()
584 };
585 let n = n - 1;
586 let V = V.rb().submatrix(1, 0, n, n);
587 let mut A = A.rb_mut().subrows_mut(1, n);
588 let H = H.as_ref();
589 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
590 V,
591 H.as_ref(),
592 if iter == 0 { Conj::Yes } else { Conj::No },
593 A.rb_mut(),
594 Par::Seq,
595 MemStack::new(&mut MemBuffer::new(
596 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(n, b, n + 1),
597 )),
598 );
599 }
600 let approx_eq = CwiseMat(ApproxEq::<f64>::eps());
601 for j in 0..n {
602 for i in 0..n {
603 if i > j + 1 || j > i + 1 {
604 V[(i, j)] = 0.0;
605 }
606 }
607 }
608 for i in 0..n {
609 if i + 1 < n {
610 V[(i, i + 1)] = V[(i + 1, i)];
611 }
612 }
613 assert!(V ~ A);
614 }
615 }
616 #[test]
617 fn test_tridiag_cplx() {
618 let rng = &mut StdRng::seed_from_u64(0);
619 for n in [2, 3, 4, 8, 16] {
620 let A = CwiseMatDistribution {
621 nrows: n,
622 ncols: n,
623 dist: ComplexDistribution::new(StandardNormal, StandardNormal),
624 }
625 .rand::<Mat<c64>>(rng);
626 let A = A.rb() + A.adjoint();
627 let b = 3;
628 let mut H = Mat::zeros(b, n - 1);
629 let mut V = A.clone();
630 let mut V = V.as_mut();
631 tridiag_in_place(
632 V.rb_mut(),
633 H.as_mut(),
634 Par::Seq,
635 MemStack::new(&mut MemBuffer::new(tridiag_in_place_scratch::<
636 c64,
637 >(
638 n, ParShape::Seq, default()
639 ))),
640 default(),
641 );
642 let mut A = A.clone();
643 let mut A = A.as_mut();
644 for iter in 0..2 {
645 let mut A = if iter == 0 {
646 A.rb_mut()
647 } else {
648 A.rb_mut().transpose_mut()
649 };
650 let n = n - 1;
651 let V = V.rb().submatrix(1, 0, n, n);
652 let mut A = A.rb_mut().subrows_mut(1, n);
653 let H = H.as_ref();
654 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
655 V,
656 H.as_ref(),
657 if iter == 0 { Conj::Yes } else { Conj::No },
658 A.rb_mut(),
659 Par::Seq,
660 MemStack::new(&mut MemBuffer::new(
661 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<c64>(n, b, n + 1),
662 )),
663 );
664 }
665 let approx_eq = CwiseMat(ApproxEq::eps());
666 for j in 0..n {
667 for i in 0..n {
668 if i > j + 1 || j > i + 1 {
669 V[(i, j)] = c64::ZERO;
670 }
671 }
672 }
673 for i in 0..n {
674 if i + 1 < n {
675 V[(i, i + 1)] = V[(i + 1, i)].conj();
676 }
677 }
678 assert!(V ~ A);
679 }
680 }
681}