mesh_sieve/accelerator/
csr.rs1use std::collections::HashMap;
4
5use crate::algs::assembly::GlobalCsrPattern;
6use crate::discretization::runtime::{ClosureDof, CsrPattern};
7
8use super::plan::{checked_u32, upload};
9use super::{AcceleratorBackend, AcceleratorError, CpuBackend, FvmScalar};
10
11pub struct DeviceCsrMatrix<T: FvmScalar, B: AcceleratorBackend> {
13 pub row_offsets: B::Buffer<u32>,
15 pub column_indices: B::Buffer<u32>,
17 pub values: B::Buffer<T>,
19 pub row_count: usize,
21 pub column_count: usize,
23 pub workspace: Option<B::Buffer<u8>>,
25}
26
27impl<T: FvmScalar, B: AcceleratorBackend> DeviceCsrMatrix<T, B> {
28 pub fn from_pattern(
30 backend: &B,
31 pattern: &CsrPattern,
32 values: &[T],
33 ) -> Result<Self, AcceleratorError> {
34 if pattern.xadj.len() != pattern.rows.len().saturating_add(1) {
35 return Err(AcceleratorError::InvalidPlan(format!(
36 "CSR has {} row offsets for {} rows",
37 pattern.xadj.len(),
38 pattern.rows.len()
39 )));
40 }
41 let indices: HashMap<ClosureDof, usize> = pattern
42 .rows
43 .iter()
44 .copied()
45 .enumerate()
46 .map(|(index, dof)| (dof, index))
47 .collect();
48 if indices.len() != pattern.rows.len() {
49 return Err(AcceleratorError::InvalidPlan(
50 "CSR pattern contains duplicate row DOFs".into(),
51 ));
52 }
53 let columns = pattern
54 .adjncy
55 .iter()
56 .map(|dof| {
57 indices.get(dof).copied().ok_or_else(|| {
58 AcceleratorError::InvalidPlan(format!(
59 "CSR column {dof:?} has no represented row"
60 ))
61 })
62 })
63 .collect::<Result<Vec<_>, _>>()?;
64 Self::from_indices(backend, &pattern.xadj, &columns, values, pattern.rows.len())
65 }
66
67 pub fn from_global_pattern(
72 backend: &B,
73 pattern: &GlobalCsrPattern,
74 values: &[T],
75 ) -> Result<Self, AcceleratorError> {
76 if pattern.xadj.len() != pattern.rows.len().saturating_add(1) {
77 return Err(AcceleratorError::InvalidPlan(format!(
78 "global CSR has {} row offsets for {} rows",
79 pattern.xadj.len(),
80 pattern.rows.len()
81 )));
82 }
83 let column_count = pattern
84 .adjncy
85 .iter()
86 .copied()
87 .max()
88 .map_or(Ok(0), |value| {
89 value.checked_add(1).ok_or(AcceleratorError::IndexOverflow {
90 what: "global CSR column count",
91 value,
92 })
93 })?;
94 Self::from_indices(
95 backend,
96 &pattern.xadj,
97 &pattern.adjncy,
98 values,
99 column_count,
100 )
101 }
102
103 fn from_indices(
104 backend: &B,
105 offsets: &[usize],
106 columns: &[usize],
107 values: &[T],
108 column_count: usize,
109 ) -> Result<Self, AcceleratorError> {
110 if offsets.is_empty() || offsets[0] != 0 {
111 return Err(AcceleratorError::InvalidPlan(
112 "CSR row offsets must begin at zero".into(),
113 ));
114 }
115 if offsets.windows(2).any(|pair| pair[0] > pair[1]) {
116 return Err(AcceleratorError::InvalidPlan(
117 "CSR row offsets must be monotone".into(),
118 ));
119 }
120 if offsets.last().copied() != Some(columns.len()) || values.len() != columns.len() {
121 return Err(AcceleratorError::InvalidPlan(format!(
122 "CSR terminal offset/column/value lengths disagree: {:?}/{}/{}",
123 offsets.last(),
124 columns.len(),
125 values.len()
126 )));
127 }
128 if let Some(&column) = columns.iter().find(|&&column| column >= column_count) {
129 return Err(AcceleratorError::InvalidPlan(format!(
130 "CSR column {column} is outside column count {column_count}"
131 )));
132 }
133 checked_u32(offsets.len(), "CSR offset count")?;
134 checked_u32(columns.len(), "CSR nonzero count")?;
135 if let Some(&value) = offsets.iter().find(|&&value| value > i32::MAX as usize) {
140 return Err(AcceleratorError::IndexOverflow {
141 what: "CSR row offset",
142 value,
143 });
144 }
145 if let Some(&value) = columns.iter().find(|&&value| value > i32::MAX as usize) {
146 return Err(AcceleratorError::IndexOverflow {
147 what: "CSR column index",
148 value,
149 });
150 }
151 let row_offsets = offsets
152 .iter()
153 .map(|&value| checked_u32(value, "CSR row offset"))
154 .collect::<Result<Vec<_>, _>>()?;
155 let column_indices = columns
156 .iter()
157 .map(|&value| checked_u32(value, "CSR column index"))
158 .collect::<Result<Vec<_>, _>>()?;
159 Ok(Self {
160 row_offsets: upload(backend, &row_offsets)?,
161 column_indices: upload(backend, &column_indices)?,
162 values: upload(backend, values)?,
163 row_count: offsets.len() - 1,
164 column_count,
165 workspace: None,
166 })
167 }
168}
169
170impl<T: FvmScalar> DeviceCsrMatrix<T, CpuBackend> {
171 pub fn spmv(
173 &self,
174 alpha: T,
175 x: &<CpuBackend as AcceleratorBackend>::Buffer<T>,
176 beta: T,
177 y: &mut <CpuBackend as AcceleratorBackend>::Buffer<T>,
178 ) -> Result<(), AcceleratorError> {
179 if x.as_slice().len() != self.column_count {
180 return Err(AcceleratorError::LengthMismatch {
181 expected: self.column_count,
182 found: x.as_slice().len(),
183 });
184 }
185 if y.as_slice().len() != self.row_count {
186 return Err(AcceleratorError::LengthMismatch {
187 expected: self.row_count,
188 found: y.as_slice().len(),
189 });
190 }
191 for row in 0..self.row_count {
192 let mut sum = 0.0;
193 for offset in self.row_offsets.as_slice()[row] as usize
194 ..self.row_offsets.as_slice()[row + 1] as usize
195 {
196 let column = self.column_indices.as_slice()[offset] as usize;
197 sum += self.values.as_slice()[offset].to_f64() * x.as_slice()[column].to_f64();
198 }
199 let previous = y.as_slice()[row].to_f64();
200 y.as_mut_slice()[row] = T::from_f64(alpha.to_f64() * sum + beta.to_f64() * previous);
201 }
202 Ok(())
203 }
204}