sublinear_solver/sublinear/
sublinear_neumann.rs1use crate::matrix::Matrix;
10use crate::types::{Precision, ErrorBounds, ErrorBoundMethod};
11use crate::error::{SolverError, Result};
12use crate::solver::{SolverAlgorithm, SolverOptions, SolverResult, SolverState, StepResult};
13use crate::sublinear::{SublinearConfig, SublinearSolver, ComplexityBound};
14use crate::sublinear::johnson_lindenstrauss::JLEmbedding;
15use alloc::{vec::Vec, string::String};
16use core::cmp;
17
18#[derive(Debug, Clone)]
20pub struct SublinearNeumannSolver {
21 config: SublinearConfig,
23 max_terms: usize,
25 series_tolerance: Precision,
27 verify_bounds: bool,
29}
30
31impl SublinearNeumannSolver {
32 pub fn new(config: SublinearConfig) -> Self {
34 Self {
35 max_terms: (config.target_dimension as f64).log2().ceil() as usize + 5,
37 series_tolerance: 1e-10,
38 verify_bounds: true,
39 config,
40 }
41 }
42
43 pub fn with_max_terms(mut self, max_terms: usize) -> Self {
45 self.max_terms = max_terms;
46 self
47 }
48
49 pub fn solve_sublinear_guaranteed(
61 &self,
62 matrix: &dyn Matrix,
63 b: &[Precision],
64 ) -> Result<SublinearNeumannResult> {
65 let n = matrix.rows();
66
67 let complexity_bound = self.verify_sublinear_conditions(matrix)?;
69
70 if n <= self.config.base_case_threshold {
72 return self.solve_base_case(matrix, b);
73 }
74
75 let jl_embedding = JLEmbedding::new(
77 n,
78 self.config.jl_distortion,
79 Some(42), )?;
81
82 let (reduced_matrix, reduced_b) = self.create_reduced_problem(matrix, b, &jl_embedding)?;
84
85 let reduced_solution = self.solve_reduced_system(&reduced_matrix, &reduced_b)?;
87
88 let reconstructed = jl_embedding.reconstruct_vector(&reduced_solution.solution)?;
90
91 let final_solution = self.apply_error_correction(
93 matrix,
94 b,
95 &reconstructed,
96 )?;
97
98 Ok(SublinearNeumannResult {
99 solution: final_solution,
100 iterations: reduced_solution.iterations,
101 residual_norm: reduced_solution.residual_norm,
102 complexity_bound,
103 dimension_reduction_ratio: jl_embedding.compression_ratio(),
104 series_terms_used: reduced_solution.series_terms_used,
105 reconstruction_error: reduced_solution.reconstruction_error,
106 })
107 }
108
109 fn create_reduced_problem(
111 &self,
112 matrix: &dyn Matrix,
113 b: &[Precision],
114 jl_embedding: &JLEmbedding,
115 ) -> Result<(Vec<Vec<Precision>>, Vec<Precision>)> {
116 let n = matrix.rows();
117
118 let mut matrix_rows = Vec::new();
120 for i in 0..n {
121 let mut row = vec![0.0; n];
122 for j in 0..n {
123 if let Some(val) = matrix.get(i, j) {
124 row[j] = val;
125 }
126 }
127 matrix_rows.push(row);
128 }
129
130 let reduced_matrix = jl_embedding.project_matrix(&matrix_rows)?;
132 let reduced_b = jl_embedding.project_vector(b)?;
133
134 Ok((reduced_matrix, reduced_b))
135 }
136
137 fn solve_reduced_system(
139 &self,
140 matrix: &[Vec<Precision>],
141 b: &[Precision],
142 ) -> Result<ReducedSolutionResult> {
143 let k = matrix.len();
144
145 let mut diagonal_inv = vec![0.0; k];
147 for i in 0..k {
148 if matrix[i][i].abs() < 1e-14 {
149 return Err(SolverError::InvalidInput {
150 message: format!("Near-zero diagonal element at position {}", i),
151 parameter: Some("matrix_diagonal".to_string()),
152 });
153 }
154 diagonal_inv[i] = 1.0 / matrix[i][i];
155 }
156
157 let scaled_b: Vec<Precision> = b.iter()
159 .zip(&diagonal_inv)
160 .map(|(&b_val, &d_inv)| b_val * d_inv)
161 .collect();
162
163 let mut solution = scaled_b.clone(); let mut current_term = scaled_b.clone();
166 let mut series_terms_used = 1;
167
168 let max_terms = cmp::min(self.max_terms, (k as f64).log2().ceil() as usize + 3);
170
171 for term_idx in 1..max_terms {
172 let mut temp = vec![0.0; k];
174
175 for i in 0..k {
177 for j in 0..k {
178 temp[i] += matrix[i][j] * current_term[j];
179 }
180 }
181
182 for i in 0..k {
184 temp[i] *= diagonal_inv[i];
185 }
186
187 for i in 0..k {
189 current_term[i] -= temp[i];
190 }
191
192 for i in 0..k {
194 solution[i] += current_term[i];
195 }
196
197 series_terms_used += 1;
198
199 let term_norm = current_term.iter()
201 .map(|x| x * x)
202 .sum::<Precision>()
203 .sqrt();
204
205 if term_norm < self.series_tolerance {
206 break;
207 }
208 }
209
210 let mut residual = vec![0.0; k];
212 for i in 0..k {
213 for j in 0..k {
214 residual[i] += matrix[i][j] * solution[j];
215 }
216 residual[i] -= b[i];
217 }
218
219 let residual_norm = residual.iter()
220 .map(|x| x * x)
221 .sum::<Precision>()
222 .sqrt();
223
224 Ok(ReducedSolutionResult {
225 solution,
226 iterations: series_terms_used,
227 residual_norm,
228 series_terms_used,
229 reconstruction_error: 0.0, })
231 }
232
233 fn solve_base_case(
235 &self,
236 matrix: &dyn Matrix,
237 b: &[Precision],
238 ) -> Result<SublinearNeumannResult> {
239 let n = matrix.rows();
246 let mut solution = vec![0.0; n];
247 let max_iters = (self.config.max_recursion_depth * 16).max(64);
252 let mut iterations_used = 0usize;
253
254 for _ in 0..max_iters {
255 iterations_used += 1;
256 let mut new_solution = vec![0.0; n];
257
258 for i in 0..n {
260 if let Some(diag) = matrix.get(i, i) {
261 if diag.abs() > 1e-14 {
262 new_solution[i] = b[i] / diag;
263 for j in 0..n {
264 if i != j {
265 if let Some(off_diag) = matrix.get(i, j) {
266 new_solution[i] -= off_diag * solution[j] / diag;
267 }
268 }
269 }
270 }
271 }
272 }
273
274 let diff: Precision = solution.iter()
276 .zip(&new_solution)
277 .map(|(old, new)| (old - new).powi(2))
278 .sum::<Precision>()
279 .sqrt();
280
281 solution = new_solution;
282
283 if diff < 1e-14 {
284 break;
285 }
286 }
287
288 let mut residual_norm = 0.0;
290 for i in 0..n {
291 let mut res = -b[i];
292 for j in 0..n {
293 if let Some(val) = matrix.get(i, j) {
294 res += val * solution[j];
295 }
296 }
297 residual_norm += res * res;
298 }
299 residual_norm = residual_norm.sqrt();
300
301 Ok(SublinearNeumannResult {
302 solution,
303 iterations: iterations_used,
304 residual_norm,
305 complexity_bound: ComplexityBound::Logarithmic(n),
306 dimension_reduction_ratio: 1.0,
307 series_terms_used: iterations_used,
308 reconstruction_error: 0.0,
309 })
310 }
311
312 fn apply_error_correction(
314 &self,
315 matrix: &dyn Matrix,
316 b: &[Precision],
317 initial_solution: &[Precision],
318 ) -> Result<Vec<Precision>> {
319 let mut solution = initial_solution.to_vec();
321
322 let mut residual = vec![0.0; matrix.rows()];
324 for i in 0..matrix.rows() {
325 residual[i] = -b[i];
326 for j in 0..matrix.cols() {
327 if let Some(val) = matrix.get(i, j) {
328 residual[i] += val * solution[j];
329 }
330 }
331 }
332
333 for i in 0..solution.len() {
335 if let Some(diag) = matrix.get(i, i) {
336 if diag.abs() > 1e-14 {
337 solution[i] -= residual[i] / diag;
338 }
339 }
340 }
341
342 Ok(solution)
343 }
344}
345
346#[derive(Debug, Clone)]
348pub struct SublinearNeumannResult {
349 pub solution: Vec<Precision>,
350 pub iterations: usize,
351 pub residual_norm: Precision,
352 pub complexity_bound: ComplexityBound,
353 pub dimension_reduction_ratio: Precision,
354 pub series_terms_used: usize,
355 pub reconstruction_error: Precision,
356}
357
358#[derive(Debug, Clone)]
360struct ReducedSolutionResult {
361 pub solution: Vec<Precision>,
362 pub iterations: usize,
363 pub residual_norm: Precision,
364 pub series_terms_used: usize,
365 pub reconstruction_error: Precision,
366}
367
368impl SublinearSolver for SublinearNeumannSolver {
369 fn verify_sublinear_conditions(&self, matrix: &dyn Matrix) -> Result<ComplexityBound> {
370 if !matrix.is_diagonally_dominant() {
372 return Err(SolverError::MatrixNotDiagonallyDominant {
373 row: 0,
374 diagonal: 0.0,
375 off_diagonal_sum: 0.0,
376 });
377 }
378
379 Ok(ComplexityBound::Logarithmic(matrix.rows()))
381 }
382
383 fn solve_sublinear(
384 &self,
385 matrix: &dyn Matrix,
386 b: &[Precision],
387 config: &SublinearConfig,
388 ) -> Result<Vec<Precision>> {
389 let result = self.solve_sublinear_guaranteed(matrix, b)?;
390 Ok(result.solution)
391 }
392
393 fn complexity_bound(&self) -> ComplexityBound {
394 ComplexityBound::Logarithmic(self.config.target_dimension)
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401 use crate::matrix::SparseMatrix;
402
403 #[test]
404 fn test_sublinear_neumann_creation() {
405 let config = SublinearConfig::default();
406 let solver = SublinearNeumannSolver::new(config);
407 assert!(solver.max_terms > 0);
408 assert!(solver.max_terms < 20); }
410
411 #[test]
412 fn test_base_case_solving() {
413 let config = SublinearConfig {
414 base_case_threshold: 10,
415 ..SublinearConfig::default()
416 };
417 let solver = SublinearNeumannSolver::new(config);
418
419 let triplets = vec![
421 (0, 0, 3.0), (0, 1, 1.0),
422 (1, 0, 1.0), (1, 1, 3.0),
423 ];
424 let matrix = SparseMatrix::from_triplets(triplets, 2, 2).unwrap();
425 let b = vec![4.0, 4.0];
426
427 let result = solver.solve_base_case(&matrix, &b).unwrap();
428 assert_eq!(result.solution.len(), 2);
429 assert!(result.residual_norm < 1e-10);
430 }
431}