Skip to main content

faer/sparse/
utils.rs

1use crate::internal_prelude_sp::*;
2use crate::{assert, debug_assert};
3/// sorts `row_indices` and `values` simultaneously so that `row_indices` is
4/// nonincreasing.
5pub fn sort_indices<I: Index, T>(
6	col_ptr: &[I],
7	col_nnz: Option<&[I]>,
8	row_idx: &mut [I],
9	val: &mut [T],
10) {
11	assert!(col_ptr.len() > 0);
12	let n = col_ptr.len() - 1;
13	for j in 0..n {
14		let start = col_ptr[j].zx();
15		let end = col_nnz
16			.map(|nnz| start + nnz[j].zx())
17			.unwrap_or(col_ptr[j + 1].zx());
18		unsafe {
19			crate::sort::sort_indices(
20				&mut row_idx[start..end],
21				&mut val[start..end],
22			)
23		};
24	}
25}
26/// sorts and deduplicates `row_indices` and `values` simultaneously so that
27/// `row_indices` is nonincreasing and contains no duplicate indices.
28pub fn sort_dedup_indices<I: Index, T: ComplexField>(
29	col_ptr: &[I],
30	col_nnz: &mut [I],
31	row_idx: &mut [I],
32	val: &mut [T],
33) {
34	assert!(col_ptr.len() > 0);
35	let n = col_ptr.len() - 1;
36	for j in 0..n {
37		let start = col_ptr[j].zx();
38		let end = start + col_nnz[j].zx();
39		unsafe {
40			crate::sort::sort_indices(
41				&mut row_idx[start..end],
42				&mut val[start..end],
43			)
44		};
45		let mut prev = I::truncate(usize::MAX);
46		let mut writer = start;
47		let mut reader = start;
48		while reader < end {
49			let cur = row_idx[reader];
50			if cur == prev {
51				writer -= 1;
52				val[writer] = (&val[writer]) + (&val[reader]);
53			} else {
54				val[writer] = val[reader].copy();
55			}
56			prev = cur;
57			reader += 1;
58			writer += 1;
59		}
60		col_nnz[j] = I::truncate(writer - start);
61	}
62}
63/// computes the workspace layout required to apply a two sided permutation to a
64/// self-adjoint sparse matrix
65pub fn permute_self_adjoint_scratch<I: Index>(dim: usize) -> StackReq {
66	StackReq::new::<I>(dim)
67}
68/// computes the workspace layout required to apply a two sided permutation to a
69/// self-adjoint sparse matrix and deduplicate its elements
70pub fn permute_dedup_self_adjoint_scratch<I: Index>(dim: usize) -> StackReq {
71	StackReq::new::<I>(dim)
72}
73/// computes the self-adjoint permutation $P A P^\top$ of the matrix $A$
74///
75/// the result is stored in `new_col_ptrs`, `new_row_indices`
76///
77/// # note
78/// allows unsorted matrices, producing a sorted output. duplicate entries are
79/// kept, however
80pub fn permute_self_adjoint<
81	'out,
82	N: Shape,
83	I: Index,
84	T: ComplexField,
85	C: Conjugate<Canonical = T>,
86>(
87	new_val: &'out mut [T],
88	new_col_ptr: &'out mut [I],
89	new_row_idx: &'out mut [I],
90	A: SparseColMatRef<'_, I, C, N, N>,
91	perm: PermRef<'_, I, N>,
92	in_side: Side,
93	out_side: Side,
94	stack: &mut MemStack,
95) -> SparseColMatMut<'out, I, T, N, N> {
96	let n = A.nrows();
97	with_dim!(N, n.unbound());
98	permute_self_adjoint_imp(
99		new_val,
100		new_col_ptr,
101		new_row_idx,
102		A.as_shape(N, N).canonical(),
103		Conj::get::<C>(),
104		perm.as_shape(N),
105		in_side,
106		out_side,
107		true,
108		stack,
109	)
110	.as_shape_mut(n, n)
111}
112/// computes the self-adjoint permutation $P A P^\top$ of the matrix $A$ without
113/// sorting the row indices, and returns a view over it
114///
115/// the result is stored in `new_col_ptrs`, `new_row_indices`
116///
117/// # note
118/// allows unsorted matrices, producing an sorted output
119pub fn permute_self_adjoint_to_unsorted<
120	'out,
121	N: Shape,
122	I: Index,
123	T: ComplexField,
124	C: Conjugate<Canonical = T>,
125>(
126	new_val: &'out mut [T],
127	new_col_ptr: &'out mut [I],
128	new_row_idx: &'out mut [I],
129	A: SparseColMatRef<'_, I, C, N, N>,
130	perm: PermRef<'_, I, N>,
131	in_side: Side,
132	out_side: Side,
133	stack: &mut MemStack,
134) -> SparseColMatMut<'out, I, T, N, N> {
135	let n = A.nrows();
136	with_dim!(N, n.unbound());
137	permute_self_adjoint_imp(
138		new_val,
139		new_col_ptr,
140		new_row_idx,
141		A.as_shape(N, N).canonical(),
142		Conj::get::<C>(),
143		perm.as_shape(N),
144		in_side,
145		out_side,
146		false,
147		stack,
148	)
149	.as_shape_mut(n, n)
150}
151/// computes the self-adjoint permutation $P A P^\top$ of the matrix $A$ and
152/// deduplicate the elements of the output matrix
153///
154/// the result is stored in `new_col_ptrs`, `new_row_indices`
155///
156/// # note
157/// allows unsorted matrices, producing a sorted output. duplicate entries are
158/// merged
159pub fn permute_dedup_self_adjoint<
160	'out,
161	N: Shape,
162	I: Index,
163	T: ComplexField,
164	C: Conjugate<Canonical = T>,
165>(
166	new_val: &'out mut [T],
167	new_col_ptr: &'out mut [I],
168	new_row_idx: &'out mut [I],
169	A: SparseColMatRef<'_, I, C, N, N>,
170	perm: PermRef<'_, I, N>,
171	in_side: Side,
172	out_side: Side,
173	stack: &mut MemStack,
174) -> SparseColMatMut<'out, I, T, N, N> {
175	let n = A.nrows();
176	with_dim!(N, n.unbound());
177	permute_dedup_self_adjoint_imp(
178		new_val,
179		new_col_ptr,
180		new_row_idx,
181		A.as_shape(N, N).canonical(),
182		Conj::get::<C>(),
183		perm.as_shape(N),
184		in_side,
185		out_side,
186		stack,
187	)
188	.as_shape_mut(n, n)
189}
190fn permute_self_adjoint_imp<'N, 'out, I: Index, T: ComplexField>(
191	new_val: &'out mut [T],
192	new_col_ptr: &'out mut [I],
193	new_row_idx: &'out mut [I],
194	A: SparseColMatRef<'_, I, T, Dim<'N>, Dim<'N>>,
195	conj_A: Conj,
196	perm: PermRef<'_, I, Dim<'N>>,
197	in_side: Side,
198	out_side: Side,
199	sort: bool,
200	stack: &mut MemStack,
201) -> SparseColMatMut<'out, I, T, Dim<'N>, Dim<'N>> {
202	let src_to_cmp = {
203		let mask = match in_side {
204			Side::Lower => 0,
205			Side::Upper => usize::MAX,
206		};
207		move |i: usize| mask ^ i
208	};
209	let dst_to_cmp = {
210		let mask = match out_side {
211			Side::Lower => 0,
212			Side::Upper => usize::MAX,
213		};
214		move |i: usize| mask ^ i
215	};
216	let conj_A = conj_A.is_conj();
217	let N = A.ncols();
218	let n = *N;
219	assert!(new_col_ptr.len() == n + 1);
220	let (_, perm_inv) = perm.bound_arrays();
221	let (mut cur_row_pos, _) = stack.collect(repeat_n!(I::truncate(0), n));
222	let cur_row_pos = Array::from_mut(&mut cur_row_pos, N);
223	let col_counts = &mut *cur_row_pos;
224	for old_j in N.indices() {
225		let new_j = perm_inv[old_j].zx();
226		let old_j_cmp = src_to_cmp(*old_j);
227		let new_j_cmp = dst_to_cmp(*new_j);
228		for old_i in A.row_idx_of_col(old_j) {
229			let new_i = perm_inv[old_i].zx();
230			let old_i_cmp = src_to_cmp(*old_i);
231			let new_i_cmp = dst_to_cmp(*new_i);
232			if old_i_cmp >= old_j_cmp {
233				let lower = new_i_cmp >= new_j_cmp;
234				let new_j = if lower { new_j } else { new_i };
235				col_counts[new_j] += I::truncate(1);
236			}
237		}
238	}
239	new_col_ptr[0] = I::truncate(0);
240	for (count, [ci0, ci1]) in iter::zip(
241		col_counts.as_mut(),
242		windows2(Cell::as_slice_of_cells(Cell::from_mut(&mut *new_col_ptr))),
243	) {
244		let ci0 = ci0.get();
245		ci1.set(ci0 + *count);
246		*count = ci0;
247	}
248	let nnz = new_col_ptr[n].zx();
249	let new_row_idx = &mut new_row_idx[..nnz];
250	let new_val = &mut new_val[..nnz];
251	{
252		with_dim!(NNZ, nnz);
253		let new_val = Array::from_mut(new_val, NNZ);
254		let new_row_idx = Array::from_mut(new_row_idx, NNZ);
255		let conj_if = |cond: bool, x: &T| -> T {
256			if const { T::IS_REAL } {
257				x.copy()
258			} else {
259				if cond != conj_A { x.conj() } else { x.copy() }
260			}
261		};
262		for old_j in N.indices() {
263			let new_j = perm_inv[old_j].zx();
264			let old_j_cmp = src_to_cmp(*old_j);
265			let new_j_cmp = dst_to_cmp(*new_j);
266			for (old_i, val) in
267				iter::zip(A.row_idx_of_col(old_j), A.val_of_col(old_j))
268			{
269				let new_i = perm_inv[old_i].zx();
270				let old_i_cmp = src_to_cmp(*old_i);
271				let new_i_cmp = dst_to_cmp(*new_i);
272				if old_i_cmp >= old_j_cmp {
273					let lower = new_i_cmp >= new_j_cmp;
274					let (new_j, new_i) = if lower {
275						(new_j, new_i)
276					} else {
277						(new_i, new_j)
278					};
279					let cur_row_pos = &mut cur_row_pos[new_j];
280					let row_pos =
281						unsafe { Idx::new_unchecked(cur_row_pos.zx(), NNZ) };
282					*cur_row_pos += I::truncate(1);
283					new_val[row_pos] = conj_if(!lower, val);
284					new_row_idx[row_pos] = I::truncate(*new_i);
285				}
286			}
287		}
288	}
289	if sort {
290		sort_indices(new_col_ptr, None, new_row_idx, new_val);
291	}
292	unsafe {
293		SparseColMatMut::new(
294			SymbolicSparseColMatRef::new_unchecked(
295				N,
296				N,
297				new_col_ptr,
298				None,
299				new_row_idx,
300			),
301			new_val,
302		)
303	}
304}
305fn permute_dedup_self_adjoint_imp<'N, 'out, I: Index, T: ComplexField>(
306	new_val: &'out mut [T],
307	new_col_ptr: &'out mut [I],
308	new_row_idx: &'out mut [I],
309	A: SparseColMatRef<'_, I, T, Dim<'N>, Dim<'N>>,
310	conj_A: Conj,
311	perm: PermRef<'_, I, Dim<'N>>,
312	in_side: Side,
313	out_side: Side,
314	stack: &mut MemStack,
315) -> SparseColMatMut<'out, I, T, Dim<'N>, Dim<'N>> {
316	let N = A.nrows();
317	permute_self_adjoint_imp(
318		new_val,
319		new_col_ptr,
320		new_row_idx,
321		A,
322		conj_A,
323		perm,
324		in_side,
325		out_side,
326		false,
327		stack,
328	);
329	{
330		let new_col_ptr = Cell::as_slice_of_cells(Cell::from_mut(new_col_ptr));
331		let start = Array::from_ref(&new_col_ptr[..*N], N);
332		let end = Array::from_ref(&new_col_ptr[1..], N);
333		let mut writer = 0usize;
334		for j in N.indices() {
335			let start = start[j].replace(I::truncate(writer)).zx();
336			let end = end[j].get().zx();
337			unsafe {
338				crate::sort::sort_indices(
339					&mut new_row_idx[start..end],
340					&mut new_val[start..end],
341				);
342			}
343			let mut prev = I::truncate(usize::MAX);
344			let mut reader = start;
345			while reader < end {
346				let cur = new_row_idx[reader];
347				if cur == prev {
348					writer -= 1;
349					new_val[writer] = (&new_val[writer]) + (&new_val[reader]);
350				} else {
351					new_row_idx[writer] = new_row_idx[reader];
352					new_val[writer] = new_val[reader].copy();
353				}
354				prev = cur;
355				reader += 1;
356				writer += 1;
357			}
358		}
359		new_col_ptr[*N].set(I::truncate(writer));
360	}
361	unsafe {
362		SparseColMatMut::new(
363			SymbolicSparseColMatRef::new_unchecked(
364				N,
365				N,
366				new_col_ptr,
367				None,
368				new_row_idx,
369			),
370			new_val,
371		)
372	}
373}
374/// computes the workspace layout required to transpose a matrix
375pub fn transpose_scratch<I: Index>(nrows: usize, ncols: usize) -> StackReq {
376	_ = ncols;
377	StackReq::new::<usize>(nrows)
378}
379/// computes the workspace layout required to transpose a matrix and deduplicate
380/// the output elements
381pub fn transpose_dedup_scratch<I: Index>(
382	nrows: usize,
383	ncols: usize,
384) -> StackReq {
385	_ = ncols;
386	StackReq::new::<usize>(nrows).array(2)
387}
388/// computes the transpose of the matrix $A$ and returns a view over it.
389///
390/// the result is stored in `new_col_ptrs`, `new_row_indices` and `new_values`.
391///
392/// # note
393/// allows unsorted matrices, producing a sorted output. duplicate entries are
394/// kept, however
395pub fn transpose<'out, Rows: Shape, Cols: Shape, I: Index, T: Clone>(
396	new_val: &'out mut [T],
397	new_col_ptr: &'out mut [I],
398	new_row_idx: &'out mut [I],
399	A: SparseColMatRef<'_, I, T, Rows, Cols>,
400	stack: &mut MemStack,
401) -> SparseColMatMut<'out, I, T, Cols, Rows> {
402	let (m, n) = A.shape();
403	with_dim!(M, m.unbound());
404	with_dim!(N, n.unbound());
405	transpose_imp(
406		T::clone,
407		new_val,
408		new_col_ptr,
409		new_row_idx,
410		A.as_shape(M, N),
411		stack,
412	)
413	.as_shape_mut(n, m)
414}
415/// computes the adjoint of the matrix $A$ and returns a view over it.
416///
417/// the result is stored in `new_col_ptrs`, `new_row_indices` and `new_values`.
418///
419/// # note
420/// allows unsorted matrices, producing a sorted output. duplicate entries are
421/// kept, however
422pub fn adjoint<'out, Rows: Shape, Cols: Shape, I: Index, T: ComplexField>(
423	new_val: &'out mut [T],
424	new_col_ptr: &'out mut [I],
425	new_row_idx: &'out mut [I],
426	A: SparseColMatRef<'_, I, T, Rows, Cols>,
427	stack: &mut MemStack,
428) -> SparseColMatMut<'out, I, T, Cols, Rows> {
429	let (m, n) = A.shape();
430	with_dim!(M, m.unbound());
431	with_dim!(N, n.unbound());
432	transpose_imp(
433		conj::<T>,
434		new_val,
435		new_col_ptr,
436		new_row_idx,
437		A.as_shape(M, N),
438		stack,
439	)
440	.as_shape_mut(n, m)
441}
442/// computes the transpose of the matrix $A$ and returns a view over it.
443///
444/// the result is stored in `new_col_ptrs`, `new_row_indices` and `new_values`.
445///
446/// # note
447/// allows unsorted matrices, producing a sorted output. duplicate entries are
448/// merged
449pub fn transpose_dedup<
450	'out,
451	Rows: Shape,
452	Cols: Shape,
453	I: Index,
454	T: ComplexField,
455	C: Conjugate<Canonical = T>,
456>(
457	new_val: &'out mut [T],
458	new_col_ptr: &'out mut [I],
459	new_row_idx: &'out mut [I],
460	A: SparseColMatRef<'_, I, C, Rows, Cols>,
461	stack: &mut MemStack,
462) -> SparseColMatMut<'out, I, T, Cols, Rows> {
463	let (m, n) = A.shape();
464	with_dim!(M, m.unbound());
465	with_dim!(N, n.unbound());
466	transpose_dedup_imp(
467		new_val,
468		new_col_ptr,
469		new_row_idx,
470		A.as_shape(M, N),
471		stack,
472	)
473	.as_shape_mut(n, m)
474}
475fn transpose_imp<'ROWS, 'COLS, 'out, I: Index, T>(
476	clone: impl Fn(&T) -> T,
477	new_val: &'out mut [T],
478	new_col_ptr: &'out mut [I],
479	new_row_idx: &'out mut [I],
480	A: SparseColMatRef<'_, I, T, Dim<'ROWS>, Dim<'COLS>>,
481	stack: &mut MemStack,
482) -> SparseColMatMut<'out, I, T, Dim<'COLS>, Dim<'ROWS>> {
483	let (M, N) = A.shape();
484	assert!(new_col_ptr.len() == *M + 1);
485	let (mut col_count, _) = stack.collect(repeat_n!(I::truncate(0), *M));
486	let col_count = Array::from_mut(&mut col_count, M);
487	for j in N.indices() {
488		for i in A.row_idx_of_col(j) {
489			col_count[i] += I::truncate(1);
490		}
491	}
492	new_col_ptr[0] = I::truncate(0);
493	for (j, [pj0, pj1]) in iter::zip(
494		M.indices(),
495		windows2(Cell::as_slice_of_cells(Cell::from_mut(new_col_ptr))),
496	) {
497		let cj = &mut col_count[j];
498		let pj = pj0.get();
499		pj1.set(pj + *cj);
500		*cj = pj;
501	}
502	let new_row_idx = &mut new_row_idx[..new_col_ptr[*M].zx()];
503	let new_val = &mut new_val[..new_col_ptr[*M].zx()];
504	let cur_row_pos = col_count;
505	for j in N.indices() {
506		for (i, val) in iter::zip(A.row_idx_of_col(j), A.val_of_col(j)) {
507			let ci = &mut cur_row_pos[i];
508			unsafe {
509				let ci = ci.zx();
510				*new_row_idx.get_unchecked_mut(ci) = I::truncate(*j);
511				*new_val.get_unchecked_mut(ci) = clone(val);
512			}
513			*ci += I::truncate(1);
514		}
515	}
516	debug_assert!(cur_row_pos.as_ref() == &new_col_ptr[1..]);
517	unsafe {
518		SparseColMatMut::new(
519			SymbolicSparseColMatRef::new_unchecked(
520				N,
521				M,
522				new_col_ptr,
523				None,
524				new_row_idx,
525			),
526			new_val,
527		)
528	}
529}
530fn transpose_dedup_imp<
531	'ROWS,
532	'COLS,
533	'out,
534	I: Index,
535	T: ComplexField,
536	C: Conjugate<Canonical = T>,
537>(
538	new_val: &'out mut [T],
539	new_col_ptr: &'out mut [I],
540	new_row_idx: &'out mut [I],
541	A: SparseColMatRef<'_, I, C, Dim<'ROWS>, Dim<'COLS>>,
542	stack: &mut MemStack,
543) -> SparseColMatMut<'out, I, T, Dim<'COLS>, Dim<'ROWS>> {
544	let (M, N) = A.shape();
545	assert!(new_col_ptr.len() == *M + 1);
546	let A = A.canonical();
547	let sentinel = I::truncate(usize::MAX);
548	let (mut col_count, stack) = stack.collect(repeat_n!(I::truncate(0), *M));
549	let (mut last_seen, _) = stack.collect(repeat_n!(sentinel, *M));
550	let col_count = Array::from_mut(&mut col_count, M);
551	let last_seen = Array::from_mut(&mut last_seen, M);
552	for j in N.indices() {
553		for i in A.row_idx_of_col(j) {
554			let j = I::truncate(*j);
555			if last_seen[i] == j {
556				continue;
557			}
558			last_seen[i] = j;
559			col_count[i] += I::truncate(1);
560		}
561	}
562	new_col_ptr[0] = I::truncate(0);
563	for (j, [pj0, pj1]) in iter::zip(
564		M.indices(),
565		windows2(Cell::as_slice_of_cells(Cell::from_mut(new_col_ptr))),
566	) {
567		let cj = &mut col_count[j];
568		let pj = pj0.get();
569		pj1.set(pj + *cj);
570		*cj = pj;
571	}
572	last_seen.as_mut().fill(sentinel);
573	let new_row_idx = &mut new_row_idx[..new_col_ptr[*M].zx()];
574	let new_val = &mut new_val[..new_col_ptr[*M].zx()];
575	let cur_row_pos = col_count;
576	for j in N.indices() {
577		for (i, val) in iter::zip(A.row_idx_of_col(j), A.val_of_col(j)) {
578			let ci = &mut cur_row_pos[i];
579			let val = if Conj::get::<C>().is_conj() {
580				val.conj()
581			} else {
582				val.copy()
583			};
584			let j = I::truncate(*j);
585			unsafe {
586				if last_seen[i] == j {
587					let ci = ci.zx() - 1;
588					*new_val.get_unchecked_mut(ci) =
589						new_val.get_unchecked(ci) + (&val);
590				} else {
591					last_seen[i] = j;
592					*ci += I::truncate(1);
593					let ci = ci.zx() - 1;
594					{
595						*new_row_idx.get_unchecked_mut(ci) = j;
596						*new_val.get_unchecked_mut(ci) = val;
597					}
598				}
599			}
600		}
601	}
602	debug_assert!(cur_row_pos.as_ref() == &new_col_ptr[1..]);
603	unsafe {
604		SparseColMatMut::new(
605			SymbolicSparseColMatRef::new_unchecked(
606				N,
607				M,
608				new_col_ptr,
609				None,
610				new_row_idx,
611			),
612			new_val,
613		)
614	}
615}
616#[cfg(test)]
617mod tests {
618	use super::*;
619	use crate::assert;
620	use crate::stats::prelude::*;
621	use dyn_stack::MemBuffer;
622	#[test]
623	fn test_transpose() {
624		let nrows = 5;
625		let ncols = 3;
626		let A = SparseColMatRef::new(
627			SymbolicSparseColMatRef::new_unsorted_checked(
628				nrows,
629				ncols,
630				&[0usize, 4, 8, 11],
631				None,
632				&[0, 0, 2, 4, 2, 1, 1, 0, 0, 1, 3],
633			),
634			&[1.0, 2.0, 3.0, 4.0, 11.0, 12.0, 13.0, 14.0, 21.0, 22.0, 23.0],
635		);
636		let nnz = A.compute_nnz();
637		let new_col_ptr = &mut *vec![0usize; nrows + 1];
638		let new_row_idx = &mut *vec![0usize; nnz];
639		let new_val = &mut *vec![0.0; nnz];
640		{
641			let out = transpose(
642				new_val,
643				new_col_ptr,
644				new_row_idx,
645				A,
646				MemStack::new(&mut MemBuffer::new(transpose_scratch::<usize>(
647					nrows, ncols,
648				))),
649			)
650			.into_const();
651			let target = SparseColMatRef::new(
652				SymbolicSparseColMatRef::new_unsorted_checked(
653					ncols,
654					nrows,
655					&[0usize, 4, 7, 9, 10, 11],
656					None,
657					&[0, 0, 1, 2, 1, 1, 2, 0, 1, 2, 0],
658				),
659				&[1.0, 2.0, 14.0, 21.0, 12.0, 13.0, 22.0, 3.0, 11.0, 23.0, 4.0],
660			);
661			assert!(all(
662				out.col_ptr() == target.col_ptr(),
663				out.row_idx() == target.row_idx(),
664				out.val() == target.val()
665			));
666		}
667		{
668			let out = transpose_dedup(
669				new_val,
670				new_col_ptr,
671				new_row_idx,
672				A,
673				MemStack::new(&mut MemBuffer::new(transpose_dedup_scratch::<
674					usize,
675				>(nrows, ncols))),
676			)
677			.into_const();
678			let target = SparseColMatRef::new(
679				SymbolicSparseColMatRef::new_unsorted_checked(
680					ncols,
681					nrows,
682					&[0usize, 3, 5, 7, 8, 9],
683					None,
684					&[0, 1, 2, 1, 2, 0, 1, 2, 0],
685				),
686				&[3.0, 14.0, 21.0, 25.0, 22.0, 3.0, 11.0, 23.0, 4.0],
687			);
688			assert!(all(
689				out.col_ptr() == target.col_ptr(),
690				out.row_idx() == target.row_idx(),
691				out.val() == target.val()
692			));
693		}
694	}
695	#[test]
696	fn test_permute_self_adjoint() {
697		let n = 5;
698		let rng = &mut StdRng::seed_from_u64(0);
699		let diag_rng = &mut StdRng::seed_from_u64(1);
700		let mut rand = || {
701			ComplexDistribution::new(StandardNormal, StandardNormal)
702				.rand::<c64>(rng)
703		};
704		let mut rand_diag = || c64::new(StandardNormal.rand(diag_rng), 0.0);
705		let val = &[
706			rand_diag(),
707			rand_diag(),
708			rand(),
709			rand(),
710			rand(),
711			rand_diag(),
712			rand_diag(),
713			rand(),
714			rand(),
715			rand(),
716			rand(),
717			rand(),
718			rand_diag(),
719			rand(),
720			rand_diag(),
721			rand(),
722			rand(),
723		];
724		let A = SparseColMatRef::new(
725			SymbolicSparseColMatRef::new_unsorted_checked(
726				n,
727				n,
728				&[0usize, 4, 8, 11, 14, 17],
729				None,
730				&[0, 0, 2, 4, 2, 1, 1, 0, 0, 1, 3, 2, 3, 4, 4, 3, 2],
731			),
732			val,
733		);
734		let nnz = A.compute_nnz();
735		let perm_fwd = &mut *vec![0, 4, 1, 3, 2usize];
736		let perm_bwd = &mut *vec![0; 5];
737		for i in 0..n {
738			perm_bwd[perm_fwd[i]] = i;
739		}
740		let perm = PermRef::new_checked(perm_fwd, perm_bwd, n);
741		let new_col_ptr = &mut *vec![0usize; n + 1];
742		let new_row_idx = &mut *vec![0usize; nnz];
743		let new_val = &mut *vec![c64::ZERO; nnz];
744		for f in [
745			permute_self_adjoint_to_unsorted,
746			permute_self_adjoint,
747			permute_dedup_self_adjoint,
748		] {
749			for (in_side, out_side) in [
750				(Side::Lower, Side::Lower),
751				(Side::Lower, Side::Upper),
752				(Side::Upper, Side::Lower),
753				(Side::Upper, Side::Upper),
754			] {
755				let mut out = f(
756					new_val,
757					new_col_ptr,
758					new_row_idx,
759					A,
760					perm,
761					in_side,
762					out_side,
763					MemStack::new(&mut MemBuffer::new(
764						permute_self_adjoint_scratch::<usize>(n),
765					)),
766				)
767				.to_dense();
768				let mut A = A.to_dense();
769				match in_side {
770					Side::Lower => {
771						z!(&mut A).for_each_triangular_upper(
772							linalg::zip::Diag::Skip,
773							|uz!(x)| *x = c64::ZERO,
774						);
775						for j in 0..n {
776							for i in 0..j {
777								A[(i, j)] = A[(j, i)].conj();
778							}
779						}
780					},
781					Side::Upper => {
782						z!(&mut A).for_each_triangular_lower(
783							linalg::zip::Diag::Skip,
784							|uz!(x)| *x = c64::ZERO,
785						);
786						for j in 0..n {
787							for i in j + 1..n {
788								A[(i, j)] = A[(j, i)].conj();
789							}
790						}
791					},
792				}
793				match out_side {
794					Side::Lower => {
795						for j in 0..n {
796							for i in 0..j {
797								out[(i, j)] = out[(j, i)].conj();
798							}
799						}
800					},
801					Side::Upper => {
802						for j in 0..n {
803							for i in j + 1..n {
804								out[(i, j)] = out[(j, i)].conj();
805							}
806						}
807					},
808				}
809				assert!(out == perm * &A * perm.inverse());
810			}
811		}
812	}
813}