Skip to main content

faer/sparse/
ops.rs

1use super::*;
2use crate::assert;
3use crate::internal_prelude::*;
4/// returns the resulting matrix obtained by applying `f` to the elements from
5/// `lhs` and `rhs`, skipping entries that are unavailable in both of `lhs` and
6/// `rhs`.
7///
8/// # panics
9/// panics if `lhs` and `rhs` don't have matching dimensions.
10#[track_caller]
11pub fn binary_op<I: Index, T, LhsT, RhsT>(
12	lhs: SparseColMatRef<'_, I, LhsT>,
13	rhs: SparseColMatRef<'_, I, RhsT>,
14	f: impl FnMut(Option<&LhsT>, Option<&RhsT>) -> T,
15) -> Result<SparseColMat<I, T>, FaerError> {
16	assert!(lhs.nrows() == rhs.nrows());
17	assert!(lhs.ncols() == rhs.ncols());
18	let mut f = f;
19	let m = lhs.nrows();
20	let n = lhs.ncols();
21	let mut col_ptr = try_zeroed::<I>(n + 1)?;
22	let mut nnz = 0usize;
23	for j in 0..n {
24		let lhs = lhs.row_idx_of_col_raw(j);
25		let rhs = rhs.row_idx_of_col_raw(j);
26		let mut lhs_pos = 0usize;
27		let mut rhs_pos = 0usize;
28		while lhs_pos < lhs.len() && rhs_pos < rhs.len() {
29			let lhs = lhs[lhs_pos];
30			let rhs = rhs[rhs_pos];
31			lhs_pos += (lhs <= rhs) as usize;
32			rhs_pos += (rhs <= lhs) as usize;
33			nnz += 1;
34		}
35		nnz += lhs.len() - lhs_pos;
36		nnz += rhs.len() - rhs_pos;
37		col_ptr[j + 1] = I::truncate(nnz);
38	}
39	if nnz > I::Signed::MAX.zx() {
40		return Err(FaerError::IndexOverflow);
41	}
42	let mut row_idx = try_zeroed(nnz)?;
43	let mut values = alloc::vec::Vec::new();
44	values
45		.try_reserve_exact(nnz)
46		.map_err(|_| FaerError::OutOfMemory)?;
47	let mut nnz = 0usize;
48	for j in 0..n {
49		let lhs_values = lhs.val_of_col(j);
50		let rhs_values = rhs.val_of_col(j);
51		let lhs = lhs.row_idx_of_col_raw(j);
52		let rhs = rhs.row_idx_of_col_raw(j);
53		let mut lhs_pos = 0usize;
54		let mut rhs_pos = 0usize;
55		while lhs_pos < lhs.len() && rhs_pos < rhs.len() {
56			let lhs = lhs[lhs_pos];
57			let rhs = rhs[rhs_pos];
58			match lhs.cmp(&rhs) {
59				core::cmp::Ordering::Less => {
60					row_idx[nnz] = lhs;
61					values.push(f(Some(&lhs_values[lhs_pos]), None));
62				},
63				core::cmp::Ordering::Equal => {
64					row_idx[nnz] = lhs;
65					values.push(f(
66						Some(&lhs_values[lhs_pos]),
67						Some(&rhs_values[rhs_pos]),
68					));
69				},
70				core::cmp::Ordering::Greater => {
71					row_idx[nnz] = rhs;
72					values.push(f(None, Some(&rhs_values[rhs_pos])));
73				},
74			}
75			lhs_pos += (lhs <= rhs) as usize;
76			rhs_pos += (rhs <= lhs) as usize;
77			nnz += 1;
78		}
79		row_idx[nnz..nnz + lhs.len() - lhs_pos]
80			.copy_from_slice(&lhs[lhs_pos..]);
81		for src in &lhs_values[lhs_pos..lhs.len()] {
82			values.push(f(Some(src), None));
83		}
84		nnz += lhs.len() - lhs_pos;
85		row_idx[nnz..nnz + rhs.len() - rhs_pos]
86			.copy_from_slice(&rhs[rhs_pos..]);
87		for src in &rhs_values[rhs_pos..rhs.len()] {
88			values.push(f(None, Some(src)));
89		}
90		nnz += rhs.len() - rhs_pos;
91	}
92	Ok(SparseColMat::<I, T>::new(
93		SymbolicSparseColMat::<I>::new_checked(m, n, col_ptr, None, row_idx),
94		values,
95	))
96}
97/// returns the resulting matrix obtained by applying `f` to the elements from
98/// `dst` and `src` skipping entries that are unavailable in both of them.
99/// the sparsity patter of `dst` is unchanged.
100///
101/// # panics
102/// panics if `src` and `dst` don't have matching dimensions.
103/// panics if `src` contains an index that's unavailable in `dst`.
104#[track_caller]
105pub fn binary_op_assign_into<I: Index, T, SrcT>(
106	dst: SparseColMatMut<'_, I, T>,
107	src: SparseColMatRef<'_, I, SrcT>,
108	f: impl FnMut(&mut T, Option<&SrcT>),
109) {
110	{
111		assert!(dst.nrows() == src.nrows());
112		assert!(dst.ncols() == src.ncols());
113		let n = dst.ncols();
114		let mut dst = dst;
115		let mut f = f;
116		for j in 0..n {
117			let (dst, dst_val) = dst.rb_mut().parts_mut();
118			let dst_val = &mut dst_val[dst.col_range(j)];
119			let src_val = src.val_of_col(j);
120			let dst = dst.row_idx_of_col_raw(j);
121			let src = src.row_idx_of_col_raw(j);
122			let mut dst_pos = 0usize;
123			let mut src_pos = 0usize;
124			while src_pos < src.len() {
125				let src = src[src_pos];
126				if dst[dst_pos] < src {
127					f(&mut dst_val[dst_pos], None);
128					dst_pos += 1;
129					continue;
130				}
131				assert!(dst[dst_pos] == src);
132				f(&mut dst_val[dst_pos], Some(&src_val[src_pos]));
133				src_pos += 1;
134				dst_pos += 1;
135			}
136			while dst_pos < dst.len() {
137				f(&mut dst_val[dst_pos], None);
138				dst_pos += 1;
139			}
140		}
141	}
142}
143/// returns the resulting matrix obtained by applying `f` to the elements from
144/// `dst`, `lhs` and `rhs`, skipping entries that are unavailable in all of
145/// `dst`, `lhs` and `rhs`. the sparsity patter of `dst` is unchanged.
146///
147/// # panics
148/// panics if `lhs`, `rhs` and `dst` don't have matching dimensions.
149/// panics if `lhs` or `rhs` contains an index that's unavailable in `dst`.
150#[track_caller]
151pub fn ternary_op_assign_into<I: Index, T, LhsT, RhsT>(
152	dst: SparseColMatMut<'_, I, T>,
153	lhs: SparseColMatRef<'_, I, LhsT>,
154	rhs: SparseColMatRef<'_, I, RhsT>,
155	f: impl FnMut(&mut T, Option<&LhsT>, Option<&RhsT>),
156) {
157	{
158		assert!(dst.nrows() == lhs.nrows());
159		assert!(dst.ncols() == lhs.ncols());
160		assert!(dst.nrows() == rhs.nrows());
161		assert!(dst.ncols() == rhs.ncols());
162		let n = dst.ncols();
163		let mut dst = dst;
164		let mut f = f;
165		for j in 0..n {
166			let (dst, dst_val) = dst.rb_mut().parts_mut();
167			let dst_val = &mut dst_val[dst.col_range(j)];
168			let lhs_val = lhs.val_of_col(j);
169			let rhs_val = rhs.val_of_col(j);
170			let dst = dst.row_idx_of_col_raw(j);
171			let rhs = rhs.row_idx_of_col_raw(j);
172			let lhs = lhs.row_idx_of_col_raw(j);
173			let mut dst_pos = 0usize;
174			let mut lhs_pos = 0usize;
175			let mut rhs_pos = 0usize;
176			while lhs_pos < lhs.len() && rhs_pos < rhs.len() {
177				let lhs = lhs[lhs_pos];
178				let rhs = rhs[rhs_pos];
179				if dst[dst_pos] < Ord::min(lhs, rhs) {
180					f(&mut dst_val[dst_pos], None, None);
181					dst_pos += 1;
182					continue;
183				}
184				assert!(dst[dst_pos] == Ord::min(lhs, rhs));
185				match lhs.cmp(&rhs) {
186					core::cmp::Ordering::Less => {
187						f(&mut dst_val[dst_pos], Some(&lhs_val[lhs_pos]), None);
188					},
189					core::cmp::Ordering::Equal => {
190						f(
191							&mut dst_val[dst_pos],
192							Some(&lhs_val[lhs_pos]),
193							Some(&rhs_val[rhs_pos]),
194						);
195					},
196					core::cmp::Ordering::Greater => {
197						f(&mut dst_val[dst_pos], None, Some(&rhs_val[rhs_pos]));
198					},
199				}
200				lhs_pos += (lhs <= rhs) as usize;
201				rhs_pos += (rhs <= lhs) as usize;
202				dst_pos += 1;
203			}
204			while lhs_pos < lhs.len() {
205				let lhs = lhs[lhs_pos];
206				if dst[dst_pos] < lhs {
207					f(&mut dst_val[dst_pos], None, None);
208					dst_pos += 1;
209					continue;
210				}
211				f(&mut dst_val[dst_pos], Some(&lhs_val[lhs_pos]), None);
212				lhs_pos += 1;
213				dst_pos += 1;
214			}
215			while rhs_pos < rhs.len() {
216				let rhs = rhs[rhs_pos];
217				if dst[dst_pos] < rhs {
218					f(&mut dst_val[dst_pos], None, None);
219					dst_pos += 1;
220					continue;
221				}
222				f(&mut dst_val[dst_pos], None, Some(&rhs_val[rhs_pos]));
223				rhs_pos += 1;
224				dst_pos += 1;
225			}
226			while rhs_pos < rhs.len() {
227				let rhs = rhs[rhs_pos];
228				dst_pos += dst[dst_pos..].binary_search(&rhs).unwrap();
229				f(&mut dst_val[dst_pos], None, Some(&rhs_val[rhs_pos]));
230				rhs_pos += 1;
231			}
232		}
233	}
234}
235/// returns the sparsity pattern containing the union of those of `lhs` and
236/// `rhs`.
237///
238/// # panics
239/// panics if `lhs` and `rhs` don't have matching dimensions.
240#[track_caller]
241#[inline]
242pub fn union_symbolic<I: Index>(
243	lhs: SymbolicSparseColMatRef<'_, I>,
244	rhs: SymbolicSparseColMatRef<'_, I>,
245) -> Result<SymbolicSparseColMat<I>, FaerError> {
246	Ok(binary_op(
247		SparseColMatRef::<I, Symbolic>::new(
248			lhs,
249			Symbolic::materialize(lhs.compute_nnz()),
250		),
251		SparseColMatRef::<I, Symbolic>::new(
252			rhs,
253			Symbolic::materialize(rhs.compute_nnz()),
254		),
255		#[inline(always)]
256		|_, _| Symbolic,
257	)?
258	.into_parts()
259	.0)
260}
261/// returns the sum of `lhs` and `rhs`.
262///
263/// # panics
264/// panics if `lhs` and `rhs` don't have matching dimensions.
265#[track_caller]
266#[inline]
267pub fn add<
268	I: Index,
269	T: ComplexField,
270	LhsT: Conjugate<Canonical = T>,
271	RhsT: Conjugate<Canonical = T>,
272>(
273	lhs: SparseColMatRef<'_, I, LhsT>,
274	rhs: SparseColMatRef<'_, I, RhsT>,
275) -> Result<SparseColMat<I, T>, FaerError> {
276	binary_op(lhs, rhs, |lhs, rhs| {
277		match (lhs.map(Conj::apply), rhs.map(Conj::apply)) {
278			(None, None) => zero(),
279			(None, Some(rhs)) => rhs,
280			(Some(lhs), None) => lhs,
281			(Some(lhs), Some(rhs)) => lhs + rhs,
282		}
283	})
284}
285/// returns the difference of `lhs` and `rhs`.
286///
287/// # panics
288/// panics if `lhs` and `rhs` don't have matching dimensions.
289#[track_caller]
290#[inline]
291pub fn sub<
292	I: Index,
293	T: ComplexField,
294	LhsT: Conjugate<Canonical = T>,
295	RhsT: Conjugate<Canonical = T>,
296>(
297	lhs: SparseColMatRef<'_, I, LhsT>,
298	rhs: SparseColMatRef<'_, I, RhsT>,
299) -> Result<SparseColMat<I, T>, FaerError> {
300	binary_op(lhs, rhs, |lhs, rhs| {
301		match (lhs.map(Conj::apply), rhs.map(Conj::apply)) {
302			(None, None) => zero(),
303			(None, Some(rhs)) => -rhs,
304			(Some(lhs), None) => lhs,
305			(Some(lhs), Some(rhs)) => lhs - rhs,
306		}
307	})
308}
309/// computes the sum of `dst` and `src` and stores the result in `dst` without
310/// changing its symbolic structure.
311///
312/// # panics
313/// panics if `dst` and `rhs` don't have matching dimensions.
314/// panics if `rhs` contains an index that's unavailable in `dst`.
315pub fn add_assign<I: Index, T: ComplexField, RhsT: Conjugate<Canonical = T>>(
316	dst: SparseColMatMut<'_, I, T>,
317	rhs: SparseColMatRef<'_, I, RhsT>,
318) {
319	binary_op_assign_into(dst, rhs, |dst, rhs| {
320		*dst += match rhs {
321			Some(rhs) => Conj::apply(rhs),
322			None => zero(),
323		}
324	})
325}
326/// computes the difference of `dst` and `src` and stores the result in `dst`
327/// without changing its symbolic structure.
328///
329/// # panics
330/// panics if `dst` and `rhs` don't have matching dimensions.
331/// panics if `rhs` contains an index that's unavailable in `dst`.
332pub fn sub_assign<I: Index, T: ComplexField, RhsT: Conjugate<Canonical = T>>(
333	dst: SparseColMatMut<'_, I, T>,
334	rhs: SparseColMatRef<'_, I, RhsT>,
335) {
336	binary_op_assign_into(dst, rhs, |dst, rhs| {
337		*dst -= match rhs {
338			Some(rhs) => Conj::apply(rhs),
339			None => zero(),
340		}
341	})
342}
343/// computes the sum of `lhs` and `rhs`, storing the result in `dst` without
344/// changing its symbolic structure.
345///
346/// # panics
347/// panics if `dst`, `lhs` and `rhs` don't have matching dimensions.
348/// panics if `lhs` or `rhs` contains an index that's unavailable in `dst`.
349#[track_caller]
350#[inline]
351pub fn add_into<
352	I: Index,
353	T: ComplexField,
354	LhsT: Conjugate<Canonical = T>,
355	RhsT: Conjugate<Canonical = T>,
356>(
357	dst: SparseColMatMut<'_, I, T>,
358	lhs: SparseColMatRef<'_, I, LhsT>,
359	rhs: SparseColMatRef<'_, I, RhsT>,
360) {
361	ternary_op_assign_into(dst, lhs, rhs, |dst, lhs, rhs| {
362		*dst = match (lhs.map(Conj::apply), rhs.map(Conj::apply)) {
363			(None, None) => zero(),
364			(None, Some(rhs)) => rhs,
365			(Some(lhs), None) => lhs,
366			(Some(lhs), Some(rhs)) => lhs + rhs,
367		};
368	})
369}
370/// computes the difference of `lhs` and `rhs`, storing the result in `dst`
371/// without changing its symbolic structure.
372///
373/// # panics
374/// panics if `dst`, `lhs` and `rhs` don't have matching dimensions.
375/// panics if `lhs` or `rhs` contains an index that's unavailable in `dst`.
376#[track_caller]
377#[inline]
378pub fn sub_into<
379	I: Index,
380	T: ComplexField,
381	LhsT: Conjugate<Canonical = T>,
382	RhsT: Conjugate<Canonical = T>,
383>(
384	dst: SparseColMatMut<'_, I, T>,
385	lhs: SparseColMatRef<'_, I, LhsT>,
386	rhs: SparseColMatRef<'_, I, RhsT>,
387) {
388	ternary_op_assign_into(dst, lhs, rhs, |dst, lhs, rhs| {
389		*dst = match (lhs.map(Conj::apply), rhs.map(Conj::apply)) {
390			(None, None) => zero(),
391			(None, Some(rhs)) => -rhs,
392			(Some(lhs), None) => lhs,
393			(Some(lhs), Some(rhs)) => lhs - rhs,
394		};
395	})
396}