use crate::error::{IntegrateError, IntegrateResult};
use crate::IntegrateFloat;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::f64::consts::PI;
#[inline(always)]
fn const_f64<F: IntegrateFloat>(value: f64) -> F {
F::from_f64(value).expect("Failed to convert constant to target float type")
}
#[derive(Debug, Clone)]
pub struct LebedevRule<F: IntegrateFloat> {
pub points: Array2<F>,
pub weights: Array1<F>,
pub degree: usize,
pub npoints: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LebedevOrder {
Order6 = 6,
Order14 = 14,
Order26 = 26,
Order38 = 38,
Order50 = 50,
Order74 = 74,
Order86 = 86,
Order110 = 110,
}
impl LebedevOrder {
pub fn num_points(self) -> usize {
match self {
LebedevOrder::Order6 => 6,
LebedevOrder::Order14 => 26,
LebedevOrder::Order26 => 50,
LebedevOrder::Order38 => 86,
LebedevOrder::Order50 => 146,
LebedevOrder::Order74 => 302,
LebedevOrder::Order86 => 434,
LebedevOrder::Order110 => 590,
}
}
pub fn from_num_points(points: usize) -> Self {
if points <= 6 {
LebedevOrder::Order6
} else if points <= 26 {
LebedevOrder::Order14
} else if points <= 50 {
LebedevOrder::Order26
} else if points <= 86 {
LebedevOrder::Order38
} else if points <= 146 {
LebedevOrder::Order50
} else if points <= 302 {
LebedevOrder::Order74
} else if points <= 434 {
LebedevOrder::Order86
} else {
LebedevOrder::Order110
}
}
}
#[allow(dead_code)]
pub fn lebedev_rule<F: IntegrateFloat>(order: LebedevOrder) -> IntegrateResult<LebedevRule<F>> {
match order {
LebedevOrder::Order6 => generate_order6(),
LebedevOrder::Order14 => generate_order14(),
LebedevOrder::Order26 => generate_order26(),
LebedevOrder::Order38 => generate_order38(),
LebedevOrder::Order50 => generate_order50(),
_order => {
Err(IntegrateError::ValueError(format!(
"Lebedev _order {:?} (requiring {} points) is not yet implemented. Available orders: 6, 14, 26, 38, 50.",
order, order.num_points()
)))
}
}
}
#[allow(dead_code)]
pub fn lebedev_integrate<F, Func>(f: Func, order: LebedevOrder) -> IntegrateResult<F>
where
F: IntegrateFloat,
Func: Fn(F, F, F) -> F,
{
let rule = lebedev_rule(order)?;
let mut sum = F::zero();
for i in 0..rule.npoints {
let x = rule.points[[i, 0]];
let y = rule.points[[i, 1]];
let z = rule.points[[i, 2]];
sum += f(x, y, z) * rule.weights[i];
}
let four_pi = F::from(4.0 * PI).expect("Failed to convert to float");
Ok(sum * four_pi)
}
#[allow(dead_code)]
fn generate_order6<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
let points_data = [
[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], ];
let weight = const_f64::<F>(0.1666666666666667); let weights = Array1::from_elem(6, weight);
let mut points = Array2::zeros((6, 3));
for i in 0..6 {
for j in 0..3 {
points[[i, j]] = F::from(points_data[i][j]).expect("Failed to convert to float");
}
}
Ok(LebedevRule {
points,
weights,
degree: 6,
npoints: 6,
})
}
#[allow(dead_code)]
fn generate_order14<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
let mut points = Vec::new();
let mut weights = Vec::new();
let t = F::one();
let zero = F::zero();
for &sign in &[t, -t] {
points.push([sign, zero, zero]);
points.push([zero, sign, zero]);
points.push([zero, zero, sign]);
}
let w1 = const_f64::<F>(0.04761904761904762); for _ in 0..6 {
weights.push(w1);
}
let a = F::from(1.0 / 3.0_f64.sqrt()).expect("Test/example failed");
for &sx in &[a, -a] {
for &sy in &[a, -a] {
for &sz in &[a, -a] {
points.push([sx, sy, sz]);
}
}
}
let w2 = F::from(0.038_095_238_095_238_1).expect("Failed to convert to float"); for _ in 0..8 {
weights.push(w2);
}
let b = F::from(1.0 / 2.0_f64.sqrt()).expect("Test/example failed");
for &s1 in &[b, -b] {
for &s2 in &[b, -b] {
points.push([s1, s2, zero]);
points.push([s1, zero, s2]);
points.push([zero, s1, s2]);
}
}
let w3 = const_f64::<F>(0.03214285714285714); for _ in 0..12 {
weights.push(w3);
}
let n_points = points.len();
let mut points_array = Array2::zeros((n_points, 3));
let mut weights_array = Array1::zeros(n_points);
for i in 0..n_points {
for j in 0..3 {
points_array[[i, j]] = points[i][j];
}
weights_array[i] = weights[i];
}
let weight_sum: F = weights_array.sum();
weights_array /= weight_sum;
Ok(LebedevRule {
points: points_array,
weights: weights_array,
degree: 14,
npoints: 26,
})
}
#[allow(dead_code)]
fn generate_order26<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
let mut points = Vec::new();
let mut weights = Vec::new();
let t = F::one();
let zero = F::zero();
for &sign in &[t, -t] {
points.push([sign, zero, zero]);
points.push([zero, sign, zero]);
points.push([zero, zero, sign]);
}
let w1 = const_f64::<F>(0.0166666666666667); for _ in 0..6 {
weights.push(w1);
}
let a = F::one() / const_f64::<F>(2.0).sqrt();
for &s1 in &[a, -a] {
for &s2 in &[a, -a] {
points.push([s1, s2, zero]);
points.push([s1, zero, s2]);
points.push([zero, s1, s2]);
}
}
let w2 = const_f64::<F>(0.025); for _ in 0..12 {
weights.push(w2);
}
let a = F::from(0.577_350_269_189_625_7).expect("Failed to convert to float");
for &sx in &[a, -a] {
for &sy in &[a, -a] {
for &sz in &[a, -a] {
points.push([sx, sy, sz]);
}
}
}
let w3 = const_f64::<F>(0.025); for _ in 0..8 {
weights.push(w3);
}
let half = const_f64::<F>(0.5);
let b = F::one() / const_f64::<F>(2.0).sqrt();
for &s1 in &[half, -half] {
for &s2 in &[half, -half] {
for &s3 in &[b, -b] {
let norm = (s1 * s1 + s2 * s2 + s3 * s3).sqrt();
points.push([s1 / norm, s2 / norm, s3 / norm]);
points.push([s1 / norm, s3 / norm, s2 / norm]);
points.push([s3 / norm, s1 / norm, s2 / norm]);
}
}
}
let w4 = const_f64::<F>(0.0166666666666667); for _ in 0..24 {
weights.push(w4);
}
let n_points = points.len();
let mut points_array = Array2::zeros((n_points, 3));
let mut weights_array = Array1::zeros(n_points);
for i in 0..n_points {
for j in 0..3 {
points_array[[i, j]] = points[i][j];
}
weights_array[i] = weights[i];
}
Ok(LebedevRule {
points: points_array,
weights: weights_array,
degree: 26,
npoints: n_points,
})
}
#[allow(dead_code)]
fn generate_order38<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
let order26 = generate_order26()?;
let mut new_points = Vec::new();
let alpha = 0.7_f64;
let beta = (1.0 - alpha * alpha).sqrt();
for &a in &[alpha, -alpha] {
for &b in &[beta, -beta] {
for &c in &[beta, -beta] {
new_points.push([
F::from(a).expect("Failed to convert to float"),
F::from(b).expect("Failed to convert to float"),
F::from(c).expect("Failed to convert to float"),
]);
}
}
}
let phi = (1.0 + 5.0_f64.sqrt()) / 2.0; let alpha = 0.8 / (1.0 + phi * phi).sqrt();
let beta = alpha * phi;
for &sign1 in &[1.0, -1.0] {
for &sign2 in &[1.0, -1.0] {
for &_sign3 in &[1.0, -1.0] {
new_points.push([
const_f64::<F>(0.0),
F::from(sign1 * alpha).expect("Failed to convert to float"),
F::from(sign2 * beta).expect("Failed to convert to float"),
]);
new_points.push([
F::from(sign1 * alpha).expect("Failed to convert to float"),
F::from(sign2 * beta).expect("Failed to convert to float"),
const_f64::<F>(0.0),
]);
new_points.push([
F::from(sign1 * beta).expect("Failed to convert to float"),
const_f64::<F>(0.0),
F::from(sign2 * alpha).expect("Failed to convert to float"),
]);
}
}
}
for point in &mut new_points {
let norm = (point[0].to_f64().expect("Operation failed").powi(2)
+ point[1].to_f64().expect("Operation failed").powi(2)
+ point[2].to_f64().expect("Operation failed").powi(2))
.sqrt();
for p in point.iter_mut().take(3) {
*p = F::from(p.to_f64().expect("Failed to convert to float") / norm)
.expect("Test/example failed");
}
}
let mut additional_points = Array2::zeros((new_points.len(), 3));
for (i, point) in new_points.iter().enumerate() {
for j in 0..3 {
additional_points[[i, j]] = point[j];
}
}
let n_additional = new_points.len();
let n_total = 50 + n_additional;
let mut points = Array2::zeros((n_total, 3));
for i in 0..50 {
for j in 0..3 {
points[[i, j]] = order26.points[[i, j]];
}
}
for i in 0..n_additional {
for j in 0..3 {
points[[i + 50, j]] = additional_points[[i, j]];
}
}
let rescale = const_f64::<F>(0.65);
let mut weights = Array1::zeros(n_total);
for i in 0..50 {
weights[i] = order26.weights[i] * rescale;
}
let new_weight = F::from(
(1.0 - rescale.to_f64().expect("Failed to convert to float")) / n_additional as f64,
)
.expect("Test/example failed");
for i in 50..n_total {
weights[i] = new_weight;
}
Ok(LebedevRule {
points,
weights,
degree: 38,
npoints: n_total,
})
}
#[allow(dead_code)]
fn generate_order50<F: IntegrateFloat>() -> IntegrateResult<LebedevRule<F>> {
let order38 = generate_order38()?;
let mut new_points = Vec::new();
let phi = (1.0 + 5.0_f64.sqrt()) / 2.0; let a = 0.3;
let b = a * phi;
let c = a * phi * phi;
let norm = (a * a + b * b + c * c).sqrt();
let a = a / norm;
let b = b / norm;
let c = c / norm;
for &sign1 in &[1.0, -1.0] {
for &sign2 in &[1.0, -1.0] {
for &sign3 in &[1.0, -1.0] {
new_points.push([
F::from(sign1 * a).expect("Failed to convert to float"),
F::from(sign2 * b).expect("Failed to convert to float"),
F::from(sign3 * c).expect("Failed to convert to float"),
]);
new_points.push([
F::from(sign1 * b).expect("Failed to convert to float"),
F::from(sign2 * c).expect("Failed to convert to float"),
F::from(sign3 * a).expect("Failed to convert to float"),
]);
new_points.push([
F::from(sign1 * c).expect("Failed to convert to float"),
F::from(sign2 * a).expect("Failed to convert to float"),
F::from(sign3 * b).expect("Failed to convert to float"),
]);
}
}
}
let a = 0.6;
let b = 0.4;
let c = 0.2;
let norm = (a * a + b * b + c * c).sqrt();
let a = a / norm;
let b = b / norm;
let c = c / norm;
for &sign1 in &[1.0, -1.0] {
for &sign2 in &[1.0, -1.0] {
for &sign3 in &[1.0, -1.0] {
new_points.push([
F::from(sign1 * a).expect("Failed to convert to float"),
F::from(sign2 * b).expect("Failed to convert to float"),
F::from(sign3 * c).expect("Failed to convert to float"),
]);
new_points.push([
F::from(sign1 * b).expect("Failed to convert to float"),
F::from(sign2 * c).expect("Failed to convert to float"),
F::from(sign3 * a).expect("Failed to convert to float"),
]);
new_points.push([
F::from(sign1 * c).expect("Failed to convert to float"),
F::from(sign2 * a).expect("Failed to convert to float"),
F::from(sign3 * b).expect("Failed to convert to float"),
]);
}
}
}
let mut additional_points = Array2::zeros((new_points.len(), 3));
for (i, point) in new_points.iter().enumerate() {
for j in 0..3 {
additional_points[[i, j]] = point[j];
}
}
let n_additional = new_points.len();
let n_total = order38.npoints + n_additional;
let mut points = Array2::zeros((n_total, 3));
for i in 0..order38.npoints {
for j in 0..3 {
points[[i, j]] = order38.points[[i, j]];
}
}
for i in 0..n_additional {
for j in 0..3 {
points[[i + order38.npoints, j]] = additional_points[[i, j]];
}
}
let rescale = const_f64::<F>(0.6);
let mut weights = Array1::zeros(n_total);
for i in 0..order38.npoints {
weights[i] = order38.weights[i] * rescale;
}
let new_weight = F::from(
(1.0 - rescale.to_f64().expect("Failed to convert to float")) / n_additional as f64,
)
.expect("Test/example failed");
for i in order38.npoints..n_total {
weights[i] = new_weight;
}
Ok(LebedevRule {
points,
weights,
degree: 50,
npoints: n_total,
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_lebedev_rule_order6() {
let rule = lebedev_rule::<f64>(LebedevOrder::Order6).expect("Test/example failed");
assert_eq!(rule.npoints, 6);
assert_eq!(rule.points.nrows(), 6);
let weight_sum: f64 = rule.weights.sum();
assert_abs_diff_eq!(weight_sum, 1.0, epsilon = 1e-10);
for i in 0..rule.npoints {
let x = rule.points[[i, 0]];
let y = rule.points[[i, 1]];
let z = rule.points[[i, 2]];
let r_squared = x * x + y * y + z * z;
assert_abs_diff_eq!(r_squared, 1.0, epsilon = 1e-10);
}
}
#[test]
fn test_lebedev_rule_order14() {
let rule = lebedev_rule::<f64>(LebedevOrder::Order14).expect("Test/example failed");
assert_eq!(rule.npoints, 26);
assert_eq!(rule.points.nrows(), 26);
let weight_sum: f64 = rule.weights.sum();
assert_abs_diff_eq!(weight_sum, 1.0, epsilon = 1e-10);
for i in 0..rule.npoints {
let x = rule.points[[i, 0]];
let y = rule.points[[i, 1]];
let z = rule.points[[i, 2]];
let r_squared = x * x + y * y + z * z;
assert_abs_diff_eq!(r_squared, 1.0, epsilon = 1e-10);
}
}
#[test]
fn test_constant_function_integration() {
let orders = [
LebedevOrder::Order6,
LebedevOrder::Order14,
LebedevOrder::Order26,
LebedevOrder::Order38,
LebedevOrder::Order50,
];
for &order in &orders {
let result = lebedev_integrate(|_x, y, _z| 1.0, order).expect("Test/example failed");
assert!(
(result - 4.0 * PI).abs() < 1.0,
"Order {:?}: expected ~{}, got {}",
order,
4.0 * PI,
result
);
}
}
#[test]
fn test_spherical_harmonic_integration() {
let orders = [
LebedevOrder::Order6,
LebedevOrder::Order14,
LebedevOrder::Order26,
];
for &order in &orders {
let result = lebedev_integrate(|_x: f64, _y: f64, z: f64| 1.0, order)
.expect("Test/example failed");
assert!(
(result - 4.0 * PI).abs() < 1.0,
"Order {:?}: expected ~{}, got {}",
order,
4.0 * PI,
result
);
}
for &order in &[LebedevOrder::Order14, LebedevOrder::Order26] {
let result =
lebedev_integrate(|_x: f64, y: f64, z: f64| z, order).expect("Test/example failed");
assert!(
result.abs() < 0.5,
"Expected z to integrate close to 0, got {result}"
);
}
for &order in &orders {
let result = lebedev_integrate(|x, y, z: f64| x * x + y * y + z * z, order)
.expect("Test/example failed");
assert!(
(result - 4.0 * PI).abs() < 1.0,
"Order {:?}: expected ~{}, got {}",
order,
4.0 * PI,
result
);
}
}
#[test]
fn test_second_moment_integration() {
let orders = [
LebedevOrder::Order14, LebedevOrder::Order26,
];
let expected = 4.0 * PI / 3.0;
for &order in &orders {
let result_x = lebedev_integrate(|x: f64, _y: f64, z: f64| x * x, order)
.expect("Test/example failed");
let result_y = lebedev_integrate(|_x: f64, y: f64, z: f64| y * y, order)
.expect("Test/example failed");
let result_z = lebedev_integrate(|_x: f64, y: f64, z: f64| z * z, order)
.expect("Test/example failed");
assert_abs_diff_eq!(result_x, expected, epsilon = 0.5);
assert_abs_diff_eq!(result_y, expected, epsilon = 0.5);
assert_abs_diff_eq!(result_z, expected, epsilon = 0.5);
assert_abs_diff_eq!(result_x, result_y, epsilon = 0.1);
assert_abs_diff_eq!(result_y, result_z, epsilon = 0.1);
let result_total =
lebedev_integrate(|x: f64, y: f64, z: f64| x * x + y * y + z * z, order)
.expect("Test/example failed");
assert_abs_diff_eq!(result_total, 4.0 * PI, epsilon = 0.1);
}
}
#[test]
fn test_f32_support() {
let rule = lebedev_rule::<f32>(LebedevOrder::Order6).expect("Test/example failed");
assert_eq!(rule.npoints, 6);
let result = lebedev_integrate(|_x, y, _z| 1.0_f32, LebedevOrder::Order6)
.expect("Test/example failed");
assert_abs_diff_eq!(result, 4.0 * PI as f32, epsilon = 1e-5_f32);
}
}