1use super::bspline::BSplineBasis;
30use crate::error::{IntegrateError, IntegrateResult};
31
32fn gauss_solve(a: &mut [Vec<f64>], b: &mut [f64], n: usize) -> IntegrateResult<Vec<f64>> {
40 for col in 0..n {
41 let mut max_row = col;
42 let mut max_val = a[col][col].abs();
43 for row in (col + 1)..n {
44 let v = a[row][col].abs();
45 if v > max_val {
46 max_val = v;
47 max_row = row;
48 }
49 }
50 if max_val < 1e-300 {
51 return Err(IntegrateError::LinearSolveError(
52 "Near-singular stiffness matrix in IGA solve".to_string(),
53 ));
54 }
55 a.swap(col, max_row);
56 b.swap(col, max_row);
57 let pivot = a[col][col];
58 for row in (col + 1)..n {
59 let factor = a[row][col] / pivot;
60 for k in col..n {
61 let sub = factor * a[col][k];
62 a[row][k] -= sub;
63 }
64 let sub_b = factor * b[col];
65 b[row] -= sub_b;
66 }
67 }
68 let mut x = vec![0.0_f64; n];
69 for i in (0..n).rev() {
70 let mut s = b[i];
71 for j in (i + 1)..n {
72 s -= a[i][j] * x[j];
73 }
74 x[i] = s / a[i][i];
75 }
76 Ok(x)
77}
78
79fn gauss_legendre(n: usize) -> (Vec<f64>, Vec<f64>) {
84 match n {
85 1 => (vec![0.0], vec![2.0]),
86 2 => (
87 vec![-0.577_350_269_189_625_8, 0.577_350_269_189_625_8],
88 vec![1.0, 1.0],
89 ),
90 3 => (
91 vec![-0.774_596_669_241_483_4, 0.0, 0.774_596_669_241_483_4],
92 vec![
93 0.555_555_555_555_556,
94 0.888_888_888_888_889,
95 0.555_555_555_555_556,
96 ],
97 ),
98 4 => (
99 vec![
100 -0.861_136_311_594_052_6,
101 -0.339_981_043_584_856_3,
102 0.339_981_043_584_856_3,
103 0.861_136_311_594_052_6,
104 ],
105 vec![
106 0.347_854_845_137_453_8,
107 0.652_145_154_862_546_1,
108 0.652_145_154_862_546_1,
109 0.347_854_845_137_453_8,
110 ],
111 ),
112 _ => gauss_legendre(4),
113 }
114}
115
116#[derive(Debug, Clone)]
122pub struct IGASolver1DConfig {
123 pub n_gauss: usize,
125}
126
127impl Default for IGASolver1DConfig {
128 fn default() -> Self {
129 Self { n_gauss: 4 }
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct IGASolution1D {
136 pub coefficients: Vec<f64>,
138 pub basis: BSplineBasis,
140}
141
142impl IGASolution1D {
143 pub fn eval(&self, t: f64) -> f64 {
145 let (span, n_vals) = self.basis.eval_basis_functions(t);
146 let p = self.basis.degree;
147 let start = span.saturating_sub(p);
148 let mut val = 0.0_f64;
149 for (k, &n_k) in n_vals.iter().enumerate() {
150 let idx = start + k;
151 if idx < self.coefficients.len() {
152 val += n_k * self.coefficients[idx];
153 }
154 }
155 val
156 }
157
158 pub fn eval_deriv(&self, t: f64) -> f64 {
160 let (span, dn_vals) = self.basis.eval_basis_derivatives(t);
161 let p = self.basis.degree;
162 let start = span.saturating_sub(p);
163 let mut val = 0.0_f64;
164 for (k, &dn_k) in dn_vals.iter().enumerate() {
165 let idx = start + k;
166 if idx < self.coefficients.len() {
167 val += dn_k * self.coefficients[idx];
168 }
169 }
170 val
171 }
172}
173
174pub struct IGASolver1D {
176 basis: BSplineBasis,
177 cfg: IGASolver1DConfig,
178}
179
180impl IGASolver1D {
181 pub fn new(degree: usize, n_elements: usize, cfg: IGASolver1DConfig) -> IntegrateResult<Self> {
189 let basis = BSplineBasis::uniform_open(degree, n_elements + degree)?;
190 Ok(Self { basis, cfg })
191 }
192
193 pub fn from_basis(basis: BSplineBasis, cfg: IGASolver1DConfig) -> Self {
195 Self { basis, cfg }
196 }
197
198 fn assemble_stiffness<A>(&self, a_coeff: &A) -> Vec<Vec<f64>>
200 where
201 A: Fn(f64) -> f64,
202 {
203 let n = self.basis.n_basis;
204 let mut k_mat = vec![vec![0.0_f64; n]; n];
205 let knots = &self.basis.knots;
206 let p = self.basis.degree;
207 let (xi_ref, w_ref) = gauss_legendre(self.cfg.n_gauss);
208
209 for span_idx in p..=(n - 1) {
211 let ta = knots[span_idx];
212 let tb = knots[span_idx + 1];
213 if (tb - ta).abs() < 1e-15 {
214 continue; }
216 let half = (tb - ta) * 0.5;
217 let mid = (ta + tb) * 0.5;
218
219 for (&xi, &w) in xi_ref.iter().zip(w_ref.iter()) {
220 let t = mid + half * xi;
221 let jac = half;
222
223 let a_val = a_coeff(t);
224 let (_, dn_vals) = self.basis.eval_basis_derivatives(t);
225 let start = span_idx.saturating_sub(p);
227
228 for (ki, &dn_i) in dn_vals.iter().enumerate() {
229 let i = start + ki;
230 if i >= n {
231 continue;
232 }
233 for (kj, &dn_j) in dn_vals.iter().enumerate() {
234 let j = start + kj;
235 if j >= n {
236 continue;
237 }
238 k_mat[i][j] += a_val * dn_i * dn_j * w * jac;
239 }
240 }
241 }
242 }
243 k_mat
244 }
245
246 fn assemble_load<F>(&self, f_rhs: &F) -> Vec<f64>
248 where
249 F: Fn(f64) -> f64,
250 {
251 let n = self.basis.n_basis;
252 let mut f_vec = vec![0.0_f64; n];
253 let knots = &self.basis.knots;
254 let p = self.basis.degree;
255 let (xi_ref, w_ref) = gauss_legendre(self.cfg.n_gauss);
256
257 for span_idx in p..=(n - 1) {
258 let ta = knots[span_idx];
259 let tb = knots[span_idx + 1];
260 if (tb - ta).abs() < 1e-15 {
261 continue;
262 }
263 let half = (tb - ta) * 0.5;
264 let mid = (ta + tb) * 0.5;
265
266 for (&xi, &w) in xi_ref.iter().zip(w_ref.iter()) {
267 let t = mid + half * xi;
268 let jac = half;
269 let f_val = f_rhs(t);
270 let (_, n_vals) = self.basis.eval_basis_functions(t);
271 let start = span_idx.saturating_sub(p);
272
273 for (ki, &n_k) in n_vals.iter().enumerate() {
274 let i = start + ki;
275 if i < n {
276 f_vec[i] += f_val * n_k * w * jac;
277 }
278 }
279 }
280 }
281 f_vec
282 }
283
284 pub fn solve<A, F>(
292 &self,
293 a_coeff: &A,
294 f_rhs: &F,
295 u0: f64,
296 u1: f64,
297 ) -> IntegrateResult<IGASolution1D>
298 where
299 A: Fn(f64) -> f64,
300 F: Fn(f64) -> f64,
301 {
302 let n = self.basis.n_basis;
303 if n < 2 {
304 return Err(IntegrateError::InvalidInput(
305 "Need at least 2 basis functions".to_string(),
306 ));
307 }
308
309 let mut k_mat = self.assemble_stiffness(a_coeff);
310 let mut f_vec = self.assemble_load(f_rhs);
311
312 for i in 1..(n - 1) {
318 f_vec[i] -= k_mat[i][0] * u0 + k_mat[i][n - 1] * u1;
319 }
320
321 let n_free = n - 2;
323 if n_free == 0 {
324 let mut coeffs = vec![0.0_f64; n];
326 coeffs[0] = u0;
327 coeffs[n - 1] = u1;
328 return Ok(IGASolution1D {
329 coefficients: coeffs,
330 basis: self.basis.clone(),
331 });
332 }
333
334 let mut k_free = vec![vec![0.0_f64; n_free]; n_free];
335 let mut f_free = vec![0.0_f64; n_free];
336 for i in 0..n_free {
337 f_free[i] = f_vec[i + 1];
338 for j in 0..n_free {
339 k_free[i][j] = k_mat[i + 1][j + 1];
340 }
341 }
342
343 let u_free = gauss_solve(&mut k_free, &mut f_free, n_free)?;
344
345 let mut coeffs = vec![0.0_f64; n];
346 coeffs[0] = u0;
347 coeffs[1..(n_free + 1)].copy_from_slice(&u_free[..n_free]);
348 coeffs[n - 1] = u1;
349
350 Ok(IGASolution1D {
351 coefficients: coeffs,
352 basis: self.basis.clone(),
353 })
354 }
355}
356
357#[derive(Debug, Clone)]
363pub struct IGASolution2D {
364 pub coefficients: Vec<Vec<f64>>,
366 pub basis_u: BSplineBasis,
368 pub basis_v: BSplineBasis,
370}
371
372impl IGASolution2D {
373 pub fn eval(&self, u: f64, v: f64) -> f64 {
375 let (span_u, n_u) = self.basis_u.eval_basis_functions(u);
376 let (span_v, n_v) = self.basis_v.eval_basis_functions(v);
377 let pu = self.basis_u.degree;
378 let pv = self.basis_v.degree;
379 let start_u = span_u.saturating_sub(pu);
380 let start_v = span_v.saturating_sub(pv);
381
382 let mut val = 0.0_f64;
383 for (ki, &n_ui) in n_u.iter().enumerate() {
384 let i = start_u + ki;
385 if i >= self.coefficients.len() {
386 continue;
387 }
388 for (kj, &n_vj) in n_v.iter().enumerate() {
389 let j = start_v + kj;
390 if j < self.coefficients[i].len() {
391 val += n_ui * n_vj * self.coefficients[i][j];
392 }
393 }
394 }
395 val
396 }
397}
398
399pub struct IGASolver2D {
401 basis_u: BSplineBasis,
402 basis_v: BSplineBasis,
403 cfg: IGASolver1DConfig,
404}
405
406impl IGASolver2D {
407 pub fn new(
409 degree: usize,
410 n_elements_u: usize,
411 n_elements_v: usize,
412 cfg: IGASolver1DConfig,
413 ) -> IntegrateResult<Self> {
414 let basis_u = BSplineBasis::uniform_open(degree, n_elements_u + degree)?;
415 let basis_v = BSplineBasis::uniform_open(degree, n_elements_v + degree)?;
416 Ok(Self {
417 basis_u,
418 basis_v,
419 cfg,
420 })
421 }
422
423 pub fn solve<F>(&self, f_rhs: &F) -> IntegrateResult<IGASolution2D>
428 where
429 F: Fn(f64, f64) -> f64,
430 {
431 let nu = self.basis_u.n_basis;
432 let nv = self.basis_v.n_basis;
433 let n_total = nu * nv;
434 let knots_u = &self.basis_u.knots;
435 let knots_v = &self.basis_v.knots;
436 let pu = self.basis_u.degree;
437 let pv = self.basis_v.degree;
438 let (xi_ref, w_ref) = gauss_legendre(self.cfg.n_gauss);
439
440 let idx = |i: usize, j: usize| i * nv + j;
442
443 let is_boundary = |i: usize, j: usize| i == 0 || i == nu - 1 || j == 0 || j == nv - 1;
445
446 let mut k_global = vec![vec![0.0_f64; n_total]; n_total];
447 let mut f_global = vec![0.0_f64; n_total];
448
449 for span_i in pu..=(nu - 1) {
451 let ua = knots_u[span_i];
452 let ub = knots_u[span_i + 1];
453 if (ub - ua).abs() < 1e-15 {
454 continue;
455 }
456 let half_u = (ub - ua) * 0.5;
457 let mid_u = (ua + ub) * 0.5;
458
459 for span_j in pv..=(nv - 1) {
460 let va = knots_v[span_j];
461 let vb = knots_v[span_j + 1];
462 if (vb - va).abs() < 1e-15 {
463 continue;
464 }
465 let half_v = (vb - va) * 0.5;
466 let mid_v = (va + vb) * 0.5;
467
468 for (&xi_u, &w_u) in xi_ref.iter().zip(w_ref.iter()) {
470 let u_pt = mid_u + half_u * xi_u;
471 let (_, n_u) = self.basis_u.eval_basis_functions(u_pt);
472 let (_, dn_u) = self.basis_u.eval_basis_derivatives(u_pt);
473 let start_u = span_i.saturating_sub(pu);
474
475 for (&xi_v, &w_v) in xi_ref.iter().zip(w_ref.iter()) {
476 let v_pt = mid_v + half_v * xi_v;
477 let (_, n_v) = self.basis_v.eval_basis_functions(v_pt);
478 let (_, dn_v) = self.basis_v.eval_basis_derivatives(v_pt);
479 let start_v = span_j.saturating_sub(pv);
480
481 let jac = half_u * half_v * w_u * w_v;
482 let f_val = f_rhs(u_pt, v_pt);
483
484 for (ki, (&n_ui, &dn_ui)) in n_u.iter().zip(dn_u.iter()).enumerate() {
486 let i = start_u + ki;
487 if i >= nu {
488 continue;
489 }
490 for (kj, (&n_vj, &dn_vj)) in n_v.iter().zip(dn_v.iter()).enumerate() {
491 let j = start_v + kj;
492 if j >= nv {
493 continue;
494 }
495 let row = idx(i, j);
496
497 f_global[row] += f_val * n_ui * n_vj * jac;
499
500 for (ki2, (&n_ui2, &dn_ui2)) in
502 n_u.iter().zip(dn_u.iter()).enumerate()
503 {
504 let i2 = start_u + ki2;
505 if i2 >= nu {
506 continue;
507 }
508 for (kj2, (&n_vj2, &dn_vj2)) in
509 n_v.iter().zip(dn_v.iter()).enumerate()
510 {
511 let j2 = start_v + kj2;
512 if j2 >= nv {
513 continue;
514 }
515 let col = idx(i2, j2);
516
517 let k_val = (dn_ui * n_vj) * (dn_ui2 * n_vj2)
522 + (n_ui * dn_vj) * (n_ui2 * dn_vj2);
523 k_global[row][col] += k_val * jac;
524 }
525 }
526 }
527 }
528 }
529 }
530 }
531 }
532
533 for i in 0..nu {
535 for j in 0..nv {
536 if is_boundary(i, j) {
537 let row = idx(i, j);
538 for k in 0..n_total {
539 k_global[row][k] = 0.0;
540 k_global[k][row] = 0.0;
541 }
542 k_global[row][row] = 1.0;
543 f_global[row] = 0.0;
544 }
545 }
546 }
547
548 let u_flat = gauss_solve(&mut k_global, &mut f_global, n_total)?;
550
551 let mut coeffs = vec![vec![0.0_f64; nv]; nu];
553 for i in 0..nu {
554 for j in 0..nv {
555 coeffs[i][j] = u_flat[idx(i, j)];
556 }
557 }
558
559 Ok(IGASolution2D {
560 coefficients: coeffs,
561 basis_u: self.basis_u.clone(),
562 basis_v: self.basis_v.clone(),
563 })
564 }
565}
566
567pub struct IGASolver;
573
574impl IGASolver {
575 pub fn solver_1d(degree: usize, n_elements: usize) -> IntegrateResult<IGASolver1D> {
577 IGASolver1D::new(degree, n_elements, IGASolver1DConfig::default())
578 }
579
580 pub fn solver_2d(
582 degree: usize,
583 n_elements_u: usize,
584 n_elements_v: usize,
585 ) -> IntegrateResult<IGASolver2D> {
586 IGASolver2D::new(
587 degree,
588 n_elements_u,
589 n_elements_v,
590 IGASolver1DConfig::default(),
591 )
592 }
593}
594
595#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn test_iga_1d_poisson_uniform() {
605 let solver = IGASolver1D::new(3, 8, IGASolver1DConfig { n_gauss: 4 })
608 .expect("IGA 1D solver creation");
609
610 let a = |_x: f64| 1.0_f64;
611 let f =
612 |x: f64| std::f64::consts::PI * std::f64::consts::PI * (std::f64::consts::PI * x).sin();
613
614 let sol = solver.solve(&a, &f, 0.0, 0.0).expect("IGA 1D solve");
615
616 let test_pts = [0.25, 0.5, 0.75];
618 for &x in &test_pts {
619 let u_iga = sol.eval(x);
620 let u_exact = (std::f64::consts::PI * x).sin();
621 let err = (u_iga - u_exact).abs();
622 assert!(
623 err < 0.05,
624 "IGA 1D u({x}) = {u_iga:.6}, exact = {u_exact:.6}, err = {err:.2e}"
625 );
626 }
627 }
628
629 #[test]
630 fn test_iga_1d_variable_coeff() {
631 let solver =
637 IGASolver1D::new(2, 4, IGASolver1DConfig { n_gauss: 3 }).expect("IGA 1D creation");
638 let a = |_x: f64| 2.0_f64;
639 let f = |_x: f64| -2.0_f64;
640 let sol = solver
641 .solve(&a, &f, 0.0, 1.0)
642 .expect("IGA 1D variable coeff solve");
643
644 for k in 0..5 {
645 let x = k as f64 * 0.2 + 0.1;
646 let u_iga = sol.eval(x);
647 let u_exact = x * (x + 1.0) / 2.0;
648 let err = (u_iga - u_exact).abs();
649 assert!(
650 err < 0.05,
651 "IGA 1D variable coeff u({x:.1}) = {u_iga:.6}, exact = {u_exact:.6}"
652 );
653 }
654 }
655
656 #[test]
657 fn test_iga_1d_solution_derivative() {
658 let solver = IGASolver1D::new(3, 6, IGASolver1DConfig::default()).expect("IGA 1D creation");
660 let a = |_x: f64| 1.0_f64;
661 let pi = std::f64::consts::PI;
662 let f = |x: f64| pi * pi * (pi * x).sin();
663 let sol = solver.solve(&a, &f, 0.0, 0.0).expect("solve");
664
665 let x = 0.3;
666 let du_iga = sol.eval_deriv(x);
667 let du_exact = pi * (pi * x).cos();
668 let err = (du_iga - du_exact).abs();
669 assert!(err < 0.3, "Derivative error = {err:.4} (too large)");
670 }
671}