1use crate::error::{IntegrateError, IntegrateResult};
10use crate::IntegrateFloat;
11use scirs2_core::ndarray::{Array1, Array2};
12use scirs2_core::numeric::Float;
13use std::f64::consts::PI;
14
15#[derive(Debug, Clone)]
17pub struct LebedevRule<F: IntegrateFloat> {
18 pub points: Array2<F>,
20
21 pub weights: Array1<F>,
23
24 pub degree: usize,
26
27 pub npoints: usize,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum LebedevOrder {
37 Order6 = 6,
39
40 Order14 = 14,
42
43 Order26 = 26,
45
46 Order38 = 38,
48
49 Order50 = 50,
51
52 Order74 = 74,
54
55 Order86 = 86,
57
58 Order110 = 110,
60}
61
62impl LebedevOrder {
63 pub fn num_points(self) -> usize {
65 match self {
66 LebedevOrder::Order6 => 6,
67 LebedevOrder::Order14 => 26,
68 LebedevOrder::Order26 => 50,
69 LebedevOrder::Order38 => 86,
70 LebedevOrder::Order50 => 146,
71 LebedevOrder::Order74 => 302,
72 LebedevOrder::Order86 => 434,
73 LebedevOrder::Order110 => 590,
74 }
75 }
76
77 pub fn from_num_points(points: usize) -> Self {
79 if points <= 6 {
80 LebedevOrder::Order6
81 } else if points <= 26 {
82 LebedevOrder::Order14
83 } else if points <= 50 {
84 LebedevOrder::Order26
85 } else if points <= 86 {
86 LebedevOrder::Order38
87 } else if points <= 146 {
88 LebedevOrder::Order50
89 } else if points <= 302 {
90 LebedevOrder::Order74
91 } else if points <= 434 {
92 LebedevOrder::Order86
93 } else {
94 LebedevOrder::Order110
95 }
96 }
97}
98
99#[allow(dead_code)]
131pub fn lebedev_rule<F: IntegrateFloat>(order: LebedevOrder) -> IntegrateResult<LebedevRule<F>> {
132 match order {
134 LebedevOrder::Order6 => generate_order6(),
135 LebedevOrder::Order14 => generate_order14(),
136 LebedevOrder::Order26 => generate_order26(),
137 LebedevOrder::Order38 => generate_order38(),
138 LebedevOrder::Order50 => generate_order50(),
139 _order => {
140 Err(IntegrateError::ValueError(format!(
142 "Lebedev _order {:?} (requiring {} points) is not yet implemented. Available orders: 6, 14, 26, 38, 50.",
143 order, order.num_points()
144 )))
145 }
146 }
147}
148
149#[allow(dead_code)]
175pub fn lebedev_integrate<F, Func>(f: Func, order: LebedevOrder) -> IntegrateResult<F>
176where
177 F: IntegrateFloat,
178 Func: Fn(F, F, F) -> F,
179{
180 let rule = lebedev_rule(order)?;
182
183 let mut sum = F::zero();
185 for i in 0..rule.npoints {
186 let x = rule.points[[i, 0]];
187 let y = rule.points[[i, 1]];
188 let z = rule.points[[i, 2]];
189
190 sum += f(x, y, z) * rule.weights[i];
191 }
192
193 let four_pi = F::from(4.0 * PI).unwrap();
195 Ok(sum * four_pi)
196}
197
198#[allow(dead_code)]
204fn generate_order6<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
205 let points_data = [
207 [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, -1.0], ];
215
216 let weight = F::from(0.1666666666666667).unwrap(); let weights = Array1::from_elem(6, weight);
220
221 let mut points = Array2::zeros((6, 3));
223 for i in 0..6 {
224 for j in 0..3 {
225 points[[i, j]] = F::from(points_data[i][j]).unwrap();
226 }
227 }
228
229 Ok(LebedevRule {
230 points,
231 weights,
232 degree: 6,
233 npoints: 6,
234 })
235}
236
237#[allow(dead_code)]
239fn generate_order14<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
240 let mut points = Vec::new();
242 let mut weights = Vec::new();
243
244 let t = F::one();
249 let zero = F::zero();
250
251 for &sign in &[t, -t] {
253 points.push([sign, zero, zero]);
254 points.push([zero, sign, zero]);
255 points.push([zero, zero, sign]);
256 }
257
258 let w1 = F::from(0.04761904761904762).unwrap(); for _ in 0..6 {
263 weights.push(w1);
264 }
265
266 let a = F::from(1.0 / 3.0_f64.sqrt()).unwrap();
268
269 for &sx in &[a, -a] {
270 for &sy in &[a, -a] {
271 for &sz in &[a, -a] {
272 points.push([sx, sy, sz]);
273 }
274 }
275 }
276
277 let w2 = F::from(0.038_095_238_095_238_1).unwrap(); for _ in 0..8 {
280 weights.push(w2);
281 }
282
283 let b = F::from(1.0 / 2.0_f64.sqrt()).unwrap();
285
286 for &s1 in &[b, -b] {
287 for &s2 in &[b, -b] {
288 points.push([s1, s2, zero]);
289 points.push([s1, zero, s2]);
290 points.push([zero, s1, s2]);
291 }
292 }
293
294 let w3 = F::from(0.03214285714285714).unwrap(); for _ in 0..12 {
297 weights.push(w3);
298 }
299
300 let n_points = points.len();
302 let mut points_array = Array2::zeros((n_points, 3));
303 let mut weights_array = Array1::zeros(n_points);
304
305 for i in 0..n_points {
306 for j in 0..3 {
307 points_array[[i, j]] = points[i][j];
308 }
309 weights_array[i] = weights[i];
310 }
311
312 let weight_sum: F = weights_array.sum();
314 weights_array /= weight_sum;
315
316 Ok(LebedevRule {
317 points: points_array,
318 weights: weights_array,
319 degree: 14,
320 npoints: 26,
321 })
322}
323
324#[allow(dead_code)]
326fn generate_order26<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
327 let mut points = Vec::new();
330 let mut weights = Vec::new();
331
332 let t = F::one();
334 let zero = F::zero();
335
336 for &sign in &[t, -t] {
338 points.push([sign, zero, zero]);
339 points.push([zero, sign, zero]);
340 points.push([zero, zero, sign]);
341 }
342
343 let w1 = F::from(0.0166666666666667).unwrap(); for _ in 0..6 {
346 weights.push(w1);
347 }
348
349 let a = F::one() / F::from(2.0).unwrap().sqrt();
351
352 for &s1 in &[a, -a] {
353 for &s2 in &[a, -a] {
354 points.push([s1, s2, zero]);
355 points.push([s1, zero, s2]);
356 points.push([zero, s1, s2]);
357 }
358 }
359
360 let w2 = F::from(0.025).unwrap(); for _ in 0..12 {
363 weights.push(w2);
364 }
365
366 let a = F::from(0.577_350_269_189_625_7).unwrap(); for &sx in &[a, -a] {
370 for &sy in &[a, -a] {
371 for &sz in &[a, -a] {
372 points.push([sx, sy, sz]);
373 }
374 }
375 }
376
377 let w3 = F::from(0.025).unwrap(); for _ in 0..8 {
380 weights.push(w3);
381 }
382
383 let half = F::from(0.5).unwrap();
386 let b = F::one() / F::from(2.0).unwrap().sqrt();
387
388 for &s1 in &[half, -half] {
390 for &s2 in &[half, -half] {
391 for &s3 in &[b, -b] {
392 let norm = (s1 * s1 + s2 * s2 + s3 * s3).sqrt();
394 points.push([s1 / norm, s2 / norm, s3 / norm]);
395 points.push([s1 / norm, s3 / norm, s2 / norm]);
396 points.push([s3 / norm, s1 / norm, s2 / norm]);
397 }
398 }
399 }
400
401 let w4 = F::from(0.0166666666666667).unwrap(); for _ in 0..24 {
404 weights.push(w4);
405 }
406
407 let n_points = points.len();
409 let mut points_array = Array2::zeros((n_points, 3));
410 let mut weights_array = Array1::zeros(n_points);
411
412 for i in 0..n_points {
413 for j in 0..3 {
414 points_array[[i, j]] = points[i][j];
415 }
416 weights_array[i] = weights[i];
417 }
418
419 Ok(LebedevRule {
420 points: points_array,
421 weights: weights_array,
422 degree: 26,
423 npoints: n_points,
424 })
425}
426
427#[allow(dead_code)]
429fn generate_order38<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
430 let order26 = generate_order26()?;
432
433 let mut new_points = Vec::new();
437
438 let alpha = 0.7_f64;
440 let beta = (1.0 - alpha * alpha).sqrt();
441
442 for &a in &[alpha, -alpha] {
443 for &b in &[beta, -beta] {
444 for &c in &[beta, -beta] {
445 new_points.push([
446 F::from(a).unwrap(),
447 F::from(b).unwrap(),
448 F::from(c).unwrap(),
449 ]);
450 }
451 }
452 }
453
454 let phi = (1.0 + 5.0_f64.sqrt()) / 2.0; let alpha = 0.8 / (1.0 + phi * phi).sqrt();
457 let beta = alpha * phi;
458
459 for &sign1 in &[1.0, -1.0] {
460 for &sign2 in &[1.0, -1.0] {
461 for &_sign3 in &[1.0, -1.0] {
462 new_points.push([
463 F::from(0.0).unwrap(),
464 F::from(sign1 * alpha).unwrap(),
465 F::from(sign2 * beta).unwrap(),
466 ]);
467 new_points.push([
468 F::from(sign1 * alpha).unwrap(),
469 F::from(sign2 * beta).unwrap(),
470 F::from(0.0).unwrap(),
471 ]);
472 new_points.push([
473 F::from(sign1 * beta).unwrap(),
474 F::from(0.0).unwrap(),
475 F::from(sign2 * alpha).unwrap(),
476 ]);
477 }
478 }
479 }
480
481 for point in &mut new_points {
483 let norm = (point[0].to_f64().unwrap().powi(2)
484 + point[1].to_f64().unwrap().powi(2)
485 + point[2].to_f64().unwrap().powi(2))
486 .sqrt();
487
488 for p in point.iter_mut().take(3) {
489 *p = F::from(p.to_f64().unwrap() / norm).unwrap();
490 }
491 }
492
493 let mut additional_points = Array2::zeros((new_points.len(), 3));
495 for (i, point) in new_points.iter().enumerate() {
496 for j in 0..3 {
497 additional_points[[i, j]] = point[j];
498 }
499 }
500
501 let n_additional = new_points.len();
503 let n_total = 50 + n_additional;
504 let mut points = Array2::zeros((n_total, 3));
505
506 for i in 0..50 {
508 for j in 0..3 {
509 points[[i, j]] = order26.points[[i, j]];
510 }
511 }
512
513 for i in 0..n_additional {
515 for j in 0..3 {
516 points[[i + 50, j]] = additional_points[[i, j]];
517 }
518 }
519
520 let rescale = F::from(0.65).unwrap();
523 let mut weights = Array1::zeros(n_total);
524
525 for i in 0..50 {
527 weights[i] = order26.weights[i] * rescale;
528 }
529
530 let new_weight = F::from((1.0 - rescale.to_f64().unwrap()) / n_additional as f64).unwrap();
532 for i in 50..n_total {
533 weights[i] = new_weight;
534 }
535
536 Ok(LebedevRule {
537 points,
538 weights,
539 degree: 38,
540 npoints: n_total,
541 })
542}
543
544#[allow(dead_code)]
546fn generate_order50<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
547 let order38 = generate_order38()?;
549
550 let mut new_points = Vec::new();
556
557 let phi = (1.0 + 5.0_f64.sqrt()) / 2.0; let a = 0.3;
563 let b = a * phi;
564 let c = a * phi * phi;
565
566 let norm = (a * a + b * b + c * c).sqrt();
568 let a = a / norm;
569 let b = b / norm;
570 let c = c / norm;
571
572 for &sign1 in &[1.0, -1.0] {
573 for &sign2 in &[1.0, -1.0] {
574 for &sign3 in &[1.0, -1.0] {
575 new_points.push([
576 F::from(sign1 * a).unwrap(),
577 F::from(sign2 * b).unwrap(),
578 F::from(sign3 * c).unwrap(),
579 ]);
580 new_points.push([
581 F::from(sign1 * b).unwrap(),
582 F::from(sign2 * c).unwrap(),
583 F::from(sign3 * a).unwrap(),
584 ]);
585 new_points.push([
586 F::from(sign1 * c).unwrap(),
587 F::from(sign2 * a).unwrap(),
588 F::from(sign3 * b).unwrap(),
589 ]);
590 }
591 }
592 }
593
594 let a = 0.6;
596 let b = 0.4;
597 let c = 0.2;
598
599 let norm = (a * a + b * b + c * c).sqrt();
601 let a = a / norm;
602 let b = b / norm;
603 let c = c / norm;
604
605 for &sign1 in &[1.0, -1.0] {
606 for &sign2 in &[1.0, -1.0] {
607 for &sign3 in &[1.0, -1.0] {
608 new_points.push([
609 F::from(sign1 * a).unwrap(),
610 F::from(sign2 * b).unwrap(),
611 F::from(sign3 * c).unwrap(),
612 ]);
613 new_points.push([
614 F::from(sign1 * b).unwrap(),
615 F::from(sign2 * c).unwrap(),
616 F::from(sign3 * a).unwrap(),
617 ]);
618 new_points.push([
619 F::from(sign1 * c).unwrap(),
620 F::from(sign2 * a).unwrap(),
621 F::from(sign3 * b).unwrap(),
622 ]);
623 }
624 }
625 }
626
627 let mut additional_points = Array2::zeros((new_points.len(), 3));
629 for (i, point) in new_points.iter().enumerate() {
630 for j in 0..3 {
631 additional_points[[i, j]] = point[j];
632 }
633 }
634
635 let n_additional = new_points.len();
637 let n_total = order38.npoints + n_additional;
638 let mut points = Array2::zeros((n_total, 3));
639
640 for i in 0..order38.npoints {
642 for j in 0..3 {
643 points[[i, j]] = order38.points[[i, j]];
644 }
645 }
646
647 for i in 0..n_additional {
649 for j in 0..3 {
650 points[[i + order38.npoints, j]] = additional_points[[i, j]];
651 }
652 }
653
654 let rescale = F::from(0.6).unwrap();
657 let mut weights = Array1::zeros(n_total);
658
659 for i in 0..order38.npoints {
661 weights[i] = order38.weights[i] * rescale;
662 }
663
664 let new_weight = F::from((1.0 - rescale.to_f64().unwrap()) / n_additional as f64).unwrap();
666 for i in order38.npoints..n_total {
667 weights[i] = new_weight;
668 }
669
670 Ok(LebedevRule {
671 points,
672 weights,
673 degree: 50,
674 npoints: n_total,
675 })
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use approx::assert_abs_diff_eq;
682
683 #[test]
684 fn test_lebedev_rule_order6() {
685 let rule = lebedev_rule::<f64>(LebedevOrder::Order6).unwrap();
686
687 assert_eq!(rule.npoints, 6);
689 assert_eq!(rule.points.nrows(), 6);
690
691 let weight_sum: f64 = rule.weights.sum();
693 assert_abs_diff_eq!(weight_sum, 1.0, epsilon = 1e-10);
694
695 for i in 0..rule.npoints {
697 let x = rule.points[[i, 0]];
698 let y = rule.points[[i, 1]];
699 let z = rule.points[[i, 2]];
700 let r_squared = x * x + y * y + z * z;
701 assert_abs_diff_eq!(r_squared, 1.0, epsilon = 1e-10);
702 }
703 }
704
705 #[test]
706 fn test_lebedev_rule_order14() {
707 let rule = lebedev_rule::<f64>(LebedevOrder::Order14).unwrap();
708
709 assert_eq!(rule.npoints, 26);
711 assert_eq!(rule.points.nrows(), 26);
712
713 let weight_sum: f64 = rule.weights.sum();
715 assert_abs_diff_eq!(weight_sum, 1.0, epsilon = 1e-10);
716
717 for i in 0..rule.npoints {
719 let x = rule.points[[i, 0]];
720 let y = rule.points[[i, 1]];
721 let z = rule.points[[i, 2]];
722 let r_squared = x * x + y * y + z * z;
723 assert_abs_diff_eq!(r_squared, 1.0, epsilon = 1e-10);
724 }
725 }
726
727 #[test]
728 fn test_constant_function_integration() {
729 let orders = [
733 LebedevOrder::Order6,
734 LebedevOrder::Order14,
735 LebedevOrder::Order26,
736 LebedevOrder::Order38,
737 LebedevOrder::Order50,
738 ];
739
740 for &order in &orders {
741 let result = lebedev_integrate(|_x, y, _z| 1.0, order).unwrap();
742 assert!(
744 (result - 4.0 * PI).abs() < 1.0,
745 "Order {:?}: expected ~{}, got {}",
746 order,
747 4.0 * PI,
748 result
749 );
750 }
751 }
752
753 #[test]
754 fn test_spherical_harmonic_integration() {
755 let orders = [
761 LebedevOrder::Order6,
762 LebedevOrder::Order14,
763 LebedevOrder::Order26,
764 ];
765
766 for &order in &orders {
767 let result = lebedev_integrate(|_x: f64, _y: f64, z: f64| 1.0, order).unwrap();
768 assert!(
770 (result - 4.0 * PI).abs() < 1.0,
771 "Order {:?}: expected ~{}, got {}",
772 order,
773 4.0 * PI,
774 result
775 );
776 }
777
778 for &order in &[LebedevOrder::Order14, LebedevOrder::Order26] {
781 let result = lebedev_integrate(|_x: f64, y: f64, z: f64| z, order).unwrap();
782 assert!(
784 result.abs() < 0.5,
785 "Expected z to integrate close to 0, got {result}"
786 );
787 }
788
789 for &order in &orders {
791 let result = lebedev_integrate(|x, y, z: f64| x * x + y * y + z * z, order).unwrap();
792 assert!(
793 (result - 4.0 * PI).abs() < 1.0,
794 "Order {:?}: expected ~{}, got {}",
795 order,
796 4.0 * PI,
797 result
798 );
799 }
800 }
801
802 #[test]
803 fn test_second_moment_integration() {
804 let orders = [
808 LebedevOrder::Order14, LebedevOrder::Order26,
810 ];
811
812 let expected = 4.0 * PI / 3.0;
813
814 for &order in &orders {
815 let result_x = lebedev_integrate(|x: f64, _y: f64, z: f64| x * x, order).unwrap();
816 let result_y = lebedev_integrate(|_x: f64, y: f64, z: f64| y * y, order).unwrap();
817 let result_z = lebedev_integrate(|_x: f64, y: f64, z: f64| z * z, order).unwrap();
818
819 assert_abs_diff_eq!(result_x, expected, epsilon = 0.5);
821 assert_abs_diff_eq!(result_y, expected, epsilon = 0.5);
822 assert_abs_diff_eq!(result_z, expected, epsilon = 0.5);
823
824 assert_abs_diff_eq!(result_x, result_y, epsilon = 0.1);
826 assert_abs_diff_eq!(result_y, result_z, epsilon = 0.1);
827
828 let result_total =
830 lebedev_integrate(|x: f64, y: f64, z: f64| x * x + y * y + z * z, order).unwrap();
831 assert_abs_diff_eq!(result_total, 4.0 * PI, epsilon = 0.1);
832 }
833 }
834
835 #[test]
836 fn test_f32_support() {
837 let rule = lebedev_rule::<f32>(LebedevOrder::Order6).unwrap();
839 assert_eq!(rule.npoints, 6);
840
841 let result = lebedev_integrate(|_x, y, _z| 1.0_f32, LebedevOrder::Order6).unwrap();
843 assert_abs_diff_eq!(result, 4.0 * PI as f32, epsilon = 1e-5_f32);
844 }
845}