math_fun/
lib.rs

1//! Special functions for science and engineering problems.
2//!
3//! Provides several mathematical functions that often appear in many different
4//! disciplines of science and engineering.
5//!
6//! The goal of this package is to provide simple-to-use, pure-rust implementations
7//! without many dependencies.
8//! Rather than trying to exhaustively provide as many functions as possible or
9//! to cover all possible argument types and ranges, implementing widely used functions
10//! in an efficient way is the first priority.
11//!
12//! Three types of input arguments are possible for indicating the position at which
13//! the function is evaluated.
14//! 1. Point evaluation: a single position
15//! e.g., `sph_bessel_kind1_ordern_arg_real(order: usize, x: f64)`,
16//! 2. Range evaluation: Three floating point arguments, `start`, `end`, and `step`
17//! to indicate the range (inclusive of `start` and exclusive of `end`).
18//! `step` can be negative, in which case `end` should be less than `start`.
19//! e.g., `sph_bessel_kind1_ordern_arg_real_ranged(order: usize, start: f64, end: f64, step: f64)`
20//! 3. Iterator evaluation: a collection of positions that implements IntoIterator
21//! e.g., `sph_bessel_kind1_ordern_arg_real_iterable(order: usize, x_list: impl IntoIterator<Item =
22//! f64>`
23
24#![warn(missing_docs)]
25
26use std::num::FpCategory::*;
27
28/// Accept the three arguments indicating a range, and return a vector of that range.
29/// It currently panics if wrong arguments are entered.
30pub fn range_to_vec(start: f64, end: f64, step: f64) -> Vec<f64> {
31    let diff: f64 = end - start;
32
33    let sds_to_vec = |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).collect::<Vec<f64>>();
34
35    match (start.classify(), diff.classify(), step.classify()) {
36        (Normal | Zero | Subnormal, Normal | Subnormal, Normal | Subnormal) => {
37            if !start.is_sign_negative() && !end.is_sign_negative() &&
38                diff.is_sign_negative() == step.is_sign_negative() {
39                sds_to_vec(start, diff, step)
40            } else {
41                panic!("Both 'start' and 'end' should be non-negative, and the sign of 'step' must match the sign of 'end - start'.");
42            }
43        },
44        (_, _, Zero) => panic!("'step' cannot be zero."),
45        (_, Zero, _) => panic!("'start' and 'end' should be different."),
46        (Infinite, _, _) | (_, Infinite, _) | (_, _, Infinite) => panic!("Arguments cannot be infinite."),
47        _ => panic!("Improper arguments.")
48    }
49}
50
51/// Spherical Bessel function of the first kind, order = 0
52/// j_n(x) = \sqrt{\pi/{2x}}J_{n+0.5}(x)
53pub fn sph_bessel_kind1_order0_arg_real(x: f64) -> f64 {
54    match x.classify() {
55        Normal | Subnormal => x.sin()/x,
56        Zero => 1.0,
57        Infinite => 0.0,
58        Nan => f64::NAN,
59    }
60
61}
62
63/// Spherical Bessel function of the first kind, order = 0, a range input
64/// It currently panics if wrong arguments are entered.
65pub fn sph_bessel_kind1_order0_arg_real_ranged(start: f64, end: f64, step: f64) -> Vec<f64> {
66    let diff: f64 = end - start;
67    let default_expr = |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize)
68        .map(|n| start + (n as f64)*step).map(|x| x.sin()/x).collect::<Vec<f64>>();
69
70    match (start.classify(), diff.classify(), step.classify()) {
71        (Normal | Subnormal, Normal | Subnormal, Normal | Subnormal) => {
72            if !start.is_sign_negative() && !end.is_sign_negative() &&
73                diff.is_sign_negative() == step.is_sign_negative() {
74                default_expr(start, diff, step)
75            } else {
76                panic!("Both 'start' and 'end' should be non-negative, and the sign of 'step' must match the sign of 'end - start'.");
77            }
78        },
79        (Zero, Normal | Subnormal, Normal | Subnormal) => {
80            let mut result = Vec::with_capacity(((diff/step).ceil() as usize) + 1);
81
82            if end.is_sign_positive() && step.is_sign_positive() {
83                result.push(1.0);
84                result.extend_from_slice(&default_expr(start + step, diff, step));
85                result
86            } else {
87                panic!("Both 'end' and 'step' should be positive if 'start' is zero.");
88            }
89        },
90        (_, _, Zero) => panic!("'step' cannot be zero."),
91        (_, Zero, _) => panic!("'start' and 'end' should be different."),
92        (Infinite, _, _) | (_, Infinite, _) | (_, _, Infinite) => panic!("Arguments cannot be infinite"),
93        _ => panic!("Improper arguments.")
94    }
95
96}
97
98/// Spherical Bessel function of the first kind, order = 0, an iterable input
99pub fn sph_bessel_kind1_order0_arg_real_iterable(x_list: impl IntoIterator<Item = f64>) -> Vec<f64> {
100    x_list.into_iter().map(|x: f64| match x.classify() {
101        Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { x.sin()/x },
102        Zero => 1.0,
103        Infinite => 0.0,
104        _ => f64::NAN
105    }).collect::<Vec<f64>>()
106}
107
108/// Spherical Bessel function of the first kind, order = n
109/// It currently panics if wrong arguments are entered.
110pub fn sph_bessel_kind1_ordern_arg_real(order: usize, x: f64) -> f64 {
111    let default_expr = match order {
112        0   => |x: f64| x.sin()/x,
113        1   => |x: f64| x.sin()/x.powi(2) - x.cos()/x,
114        2   => |x: f64| x.sin()*(3.0/x.powi(3)-1.0/x) - x.cos()*(3.0/x.powi(2)),
115        _   => panic!("Only orders from 0 to 2 are currently implemented.")
116    };
117
118    match x.classify() {
119        Normal | Subnormal => default_expr(x),
120        Zero => match order {
121            0 => 1.0,
122            _ => 0.0
123        },
124        Infinite => 0.0,
125        Nan => f64::NAN,
126    }
127}
128
129/// Spherical Bessel function of the first kind, order = n, a range input
130/// It currently panics if wrong arguments are entered.
131pub fn sph_bessel_kind1_ordern_arg_real_ranged(order: usize, start: f64, end: f64, step: f64) -> Vec<f64> {
132    // To Do: Handle the case of 'list too long' (number of points > usize.MAX)
133    let diff: f64 = end - start;
134
135    let default_expr = match order {
136        0   =>  {
137            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| x.sin()/x).collect::<Vec<f64>>()
138        },
139        1   =>  {
140            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| x.sin()/x.powi(2) - x.cos()/x).collect::<Vec<f64>>()
141        },
142        2   =>  {
143            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| x.sin()*(3.0/x.powi(3)-1.0/x) - x.cos()*(3.0/x.powi(2))).collect::<Vec<f64>>()
144        },
145        _   =>  {
146            panic!("Only orders from 0 to 2 are currently implemented.");
147        }
148    };
149
150    match (start.classify(), diff.classify(), step.classify()) {
151        (Normal | Subnormal, Normal | Subnormal, Normal | Subnormal) => {
152            if !start.is_sign_negative() && !end.is_sign_negative() && 
153                diff.is_sign_negative() == step.is_sign_negative() {
154                default_expr(start, diff, step)
155            } else {
156                panic!("Both 'start' and 'end' should be non-negative, and the sign of 'step' must match the sign of 'end - start'.");
157            }
158        },
159        (Zero, Normal | Subnormal, Normal | Subnormal) => {
160            let mut result = Vec::with_capacity(((diff/step).ceil() as usize) + 1);
161                
162            if end.is_sign_positive() && step.is_sign_positive() { 
163                result.push(match order {
164                        0       => 1.0,
165                        1..=2   => 0.0,
166                        _       => panic!("Only orders from 0 to 2 are currently implemented.")
167                });
168                result.extend_from_slice(&default_expr(start + step, diff, step));
169                result
170            } else {
171                panic!("Both 'end' and 'step' should be positive if 'start' is zero.");
172            }
173        },
174        (_, _, Zero) => panic!("'step' cannot be zero."),
175        (_, Zero, _) => panic!("'start' and 'end' should be different."),
176        (Infinite, _, _) | (_, Infinite, _) | (_, _, Infinite) => panic!("Arguments cannot be infinite."),
177        _ => panic!("Improper arguments.")
178    }
179}
180
181/// Spherical Bessel function of the first kind, order = n, an iterable input
182/// It currently panics if wrong arguments are entered.
183pub fn sph_bessel_kind1_ordern_arg_real_iterable(order: usize, x_list: impl IntoIterator<Item = f64>) -> Vec<f64> {
184    match order {
185        0   =>  {
186            x_list.into_iter().map(|x| match x.classify() {
187                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { x.sin()/x },
188                Zero => 1.0,
189                Infinite => 0.0,
190                _ => f64::NAN
191            }).collect::<Vec<f64>>()
192        },
193        1   =>  {
194            x_list.into_iter().map(|x| match x.classify() {
195                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { x.sin()/x.powi(2) - x.cos()/x },
196                Zero | Infinite => 0.0,
197                _ => f64::NAN
198            }).collect::<Vec<f64>>()
199        },
200        2   =>  {
201            x_list.into_iter().map(|x| match x.classify() {
202                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { x.sin()*(3.0/x.powi(3)-1.0/x) - x.cos()*(3.0/x.powi(2)) },
203                Zero | Infinite => 0.0,
204                _ => f64::NAN
205            }).collect::<Vec<f64>>()
206        },
207        _   =>  {
208            panic!("Only orders from 0 to 2 are currently implemented.");
209        }
210    }
211}
212
213/// Spherical Bessel function of the second kind, order = 0
214/// j_n(x) = \sqrt{\pi/{2x}}Y_{n+0.5}(x)
215pub fn sph_bessel_kind2_order0_arg_real(x: f64) -> f64 {
216    match x.classify() {
217        Normal | Subnormal => -x.cos()/x,
218        Zero => f64::INFINITY,
219        Infinite => 0.0,
220        Nan => f64::NAN,
221    }
222
223}
224
225/// Spherical Bessel function of the second kind, order = 0, a range input
226/// It currently panics if wrong arguments are entered.
227pub fn sph_bessel_kind2_order0_arg_real_ranged(start: f64, end: f64, step: f64) -> Vec<f64> {
228    let diff: f64 = end - start;
229    let default_expr = |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize)
230        .map(|n| start + (n as f64)*step).map(|x| -x.cos()/x).collect::<Vec<f64>>();
231
232    match (start.classify(), diff.classify(), step.classify()) {
233        (Normal | Subnormal, Normal | Subnormal, Normal | Subnormal) => {
234            if !start.is_sign_negative() && !end.is_sign_negative() &&
235                diff.is_sign_negative() == step.is_sign_negative() {
236                default_expr(start, diff, step)
237            } else {
238                panic!("Both 'start' and 'end' should be non-negative, and the sign of 'step' must match the sign of 'end - start'.");
239            }
240        },
241        (Zero, Normal | Subnormal, Normal | Subnormal) => {
242            let mut result = Vec::with_capacity(((diff/step).ceil() as usize) + 1);
243
244            if end.is_sign_positive() && step.is_sign_positive() {
245                result.push(f64::NEG_INFINITY);
246                result.extend_from_slice(&default_expr(start + step, diff, step));
247                result
248            } else {
249                panic!("Both 'end' and 'step' should be positive if 'start' is zero.");
250            }
251        },
252        (_, _, Zero) => panic!("'step' cannot be zero."),
253        (_, Zero, _) => panic!("'start' and 'end' should be different."),
254        (Infinite, _, _) | (_, Infinite, _) | (_, _, Infinite) => panic!("Arguments cannot be infinite"),
255        _ => panic!("Improper arguments.")
256    }
257
258}
259
260/// Spherical Bessel function of the second kind, order = 0, an iterable input
261pub fn sph_bessel_kind2_order0_arg_real_iterable(x_list: impl IntoIterator<Item = f64>) -> Vec<f64> {
262    x_list.into_iter().map(|x: f64| match x.classify() {
263        Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { -x.cos()/x },
264        Zero => f64::NEG_INFINITY,
265        Infinite => 0.0,
266        _ => f64::NAN
267    }).collect::<Vec<f64>>()
268}
269
270/// Spherical Bessel function of the second kind, order = n
271/// It currently panics if wrong arguments are entered.
272pub fn sph_bessel_kind2_ordern_arg_real(order: usize, x: f64) -> f64 {
273    let default_expr = match order {
274        0   => |x: f64| -x.cos()/x,
275        1   => |x: f64| -x.cos()/x.powi(2) - x.sin()/x,
276        2   => |x: f64| x.cos()*(-3.0/x.powi(3)+1.0/x) - x.sin()*(3.0/x.powi(2)),
277        _   => panic!("Only orders from 0 to 2 are currently implemented")
278    };
279
280    match x.classify() {
281        Normal | Subnormal => default_expr(x),
282        Zero => f64::NEG_INFINITY,
283        Infinite => 0.0,
284        Nan => f64::NAN,
285    }
286
287}
288
289/// Spherical Bessel function of the second kind, order = n, a range input
290/// It currently panics if wrong arguments are entered.
291/// In the future the return type may be changed to Results to deal with errors graciously.
292pub fn sph_bessel_kind2_ordern_arg_real_ranged(order: usize, start: f64, end: f64, step: f64) -> Vec<f64> {
293    // To Do: Handle the case of 'list too long' (number of points > usize.MAX)
294    let diff: f64 = end - start;
295
296    let default_expr = match order {
297        0   =>  {
298            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| -x.cos()/x).collect::<Vec<f64>>()
299        },
300        1   =>  {
301            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| -x.cos()/x.powi(2) - x.sin()/x).collect::<Vec<f64>>()
302        },
303        2   =>  {
304            |start: f64, diff: f64, step: f64| (0 .. (diff/step).ceil() as usize).map(|n| start + (n as f64)*step).map(|x| x.cos()*(-3.0/x.powi(3)+1.0/x) - x.sin()*(3.0/x.powi(2))).collect::<Vec<f64>>()
305        },
306        _   =>  {
307            panic!("Only orders from 0 to 2 are currently implemented.");
308        },
309    };
310 
311    match (start.classify(), diff.classify(), step.classify()) {
312        (Normal | Subnormal, Normal | Subnormal, Normal | Subnormal) => {
313            if !start.is_sign_negative() && !end.is_sign_negative() && 
314                diff.is_sign_negative() == step.is_sign_negative() {
315                default_expr(start, diff, step)
316            } else {
317                panic!("Both 'start' and 'end' should be non-negative, and the sign of 'step' must match the sign of 'end - start'.");
318            }
319        },
320        (Zero, Normal | Subnormal, Normal | Subnormal) => {
321            let mut result = Vec::with_capacity(((diff/step).ceil() as usize) + 1);
322                
323            if end.is_sign_positive() && step.is_sign_positive() { 
324                result.push(match order {
325                        0..=2   => f64::NEG_INFINITY,
326                        _       => panic!("Only orders from 0 to 2 are currently implemented.")
327                });
328                result.extend_from_slice(&default_expr(start+step, diff, step));
329                result
330            } else {
331                panic!("Both 'end' and 'step' should be positive if 'start' is zero.");
332            }
333        },
334        (_, _, Zero) => panic!("'step' cannot be zero."),
335        (_, Zero, _) => panic!("'start' and 'end' should be different."),
336        (Infinite, _, _) | (_, Infinite, _) | (_, _, Infinite) => panic!("Arguments cannot be infinite."),
337        _ => panic!("Improper arguments.")
338    }
339}
340
341/// Spherical Bessel function of the second kind, order = n, an iterable input
342/// It currently panics if wrong arguments are entered.
343pub fn sph_bessel_kind2_ordern_arg_real_iterable(order: usize, x_list: impl IntoIterator<Item = f64>) -> Vec<f64> {
344    match order {
345        0   =>  {
346            x_list.into_iter().map(|x| match x.classify() {
347                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { -x.cos()/x },
348                Zero => f64::NEG_INFINITY,
349                Infinite => 0.0,
350                _ => f64::NAN
351            }).collect::<Vec<f64>>()
352        },
353        1   =>  {
354            x_list.into_iter().map(|x| match x.classify() {
355                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { -x.cos()/x.powi(2) - x.sin()/x },
356                Zero => f64::NEG_INFINITY,
357                Infinite => 0.0,
358                _ => f64::NAN
359            }).collect::<Vec<f64>>()
360        },
361        2   =>  {
362            x_list.into_iter().map(|x| match x.classify() {
363                Normal | Subnormal => if x.is_sign_negative() { f64::NAN } else { x.cos()*(-3.0/x.powi(3)+1.0/x) - x.sin()*(3.0/x.powi(2)) },
364                Zero => f64::NEG_INFINITY,
365                Infinite => 0.0,
366                _ => f64::NAN
367            }).collect::<Vec<f64>>()
368        },
369        _   =>  {
370            panic!("Only orders from 0 to 2 are currently implemented.");
371        }
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use std::time::Instant;
379    use plotly::{
380        common::Mode,
381        layout::{Axis, Layout},
382        Plot, Scatter, ImageFormat
383    };
384
385    #[test]
386    fn benchmark_sph_bessel_kind1_order0_1million() {
387        let start_time = Instant::now();
388
389        for n in 0..1000000 {
390            // sph_bessel_1st_0_unchecked(0.5);
391            // sph_bessel_1st_0_unchecked((n as f64)/1.0e5 + 0.1);
392            sph_bessel_kind1_order0_arg_real((n as f64)/1.0e5 + 0.1);
393        }
394
395        let elapsed_time = start_time.elapsed();
396        println!("Elapsed time: {:?}", elapsed_time);
397    }
398
399    #[test]
400    fn benchmark_sph_bessel_kind1_order0_range_10million() {
401        let start_time = Instant::now();
402
403        for n in 0..1000 {
404            sph_bessel_kind1_ordern_arg_real_ranged(2, (n as f64)/2.0e5 + 0.1, 10000.0, 1.0);
405        }
406
407        let elapsed_time = start_time.elapsed();
408        println!("Elapsed time: {:?}", elapsed_time);
409    }
410
411    #[test]
412    fn benchmark_sph_bessel_kind1_order0_iterable_10million() {
413        let start_time = Instant::now();
414
415        for n in 0..1000 {
416            sph_bessel_kind1_order0_arg_real_iterable((0..10000).map(|m| ((n + m) as f64)/3.0e3)
417                .collect::<Vec<f64>>());
418        }
419
420        let elapsed_time = start_time.elapsed();
421        println!("Elapsed time: {:?}", elapsed_time);
422    }
423
424
425    #[test]
426    fn plot_result() {
427        let x_start: f64 = 0.0;
428        let x_end: f64 = 10.0;
429        let x_step: f64 = 0.05;
430
431        let x_vec: Vec<f64> = range_to_vec(x_start, x_end, x_step);
432
433        let sph_bessel2_0: Vec<f64> = sph_bessel_kind2_ordern_arg_real_ranged(0, x_start, x_end, x_step);
434        let sph_bessel2_1: Vec<f64> = sph_bessel_kind2_ordern_arg_real_ranged(1, x_start, x_end, x_step);
435        let sph_bessel2_2: Vec<f64> = sph_bessel_kind2_ordern_arg_real_ranged(2, x_start, x_end, x_step);
436
437        let trace_sb2_0 = Scatter::new(x_vec.clone(), sph_bessel2_0)
438            .mode(Mode::Lines)
439            .name("Order 0");
440        let trace_sb2_1 = Scatter::new(x_vec.clone(), sph_bessel2_1)
441            .mode(Mode::Lines)
442            .name("Order 1");
443        let trace_sb2_2 = Scatter::new(x_vec.clone(), sph_bessel2_2)
444            .mode(Mode::Lines)
445            .name("Order 2");
446
447        let mut plot_sb = Plot::new();
448        plot_sb.add_trace(trace_sb2_0);
449        plot_sb.add_trace(trace_sb2_1);
450        plot_sb.add_trace(trace_sb2_2);
451
452        let layout_sb = Layout::new()
453            .title("Spherical Bessel functions of the second kind".into())
454            .x_axis(Axis::new().range(vec![x_start, x_end]))
455            .y_axis(Axis::new().range(vec![-2.5, 1.0]));
456        plot_sb.set_layout(layout_sb);
457
458        plot_sb.write_image("./sph_bessel_kind2.png", ImageFormat::PNG, 600, 400, 1.0);
459    }
460
461    #[test]
462    fn test_sph_bessel_kind2() {
463        println!("{:?}", sph_bessel_kind2_order0_arg_real(0.1*f64::MIN_POSITIVE));
464        println!("{:?}", sph_bessel_kind2_order0_arg_real_ranged(0.0, 2.5*f64::MIN_POSITIVE, 0.5*f64::MIN_POSITIVE));
465        println!("{:?}", sph_bessel_kind2_ordern_arg_real_ranged(2, 0.0, 2.5*f64::MIN_POSITIVE, 0.5*f64::MIN_POSITIVE));
466    }
467
468    #[test]
469    fn test_sph_bessel_kind1_ord0_arg_iterable() {
470        let x_start: f64 = 0.0;
471        let x_end: f64 = 10.0;
472        let x_step: f64 = 0.1;
473
474        let x_vec: Vec<f64> = range_to_vec(x_start, x_end, x_step);
475
476        let sb10_r: Vec<f64> = sph_bessel_kind1_order0_arg_real_ranged(x_start, x_end, x_step);
477        let sb10_i: Vec<f64> = sph_bessel_kind1_order0_arg_real_iterable(x_vec.clone());
478
479        let trace_sb10_r = Scatter::new(x_vec.clone(), sb10_r)
480            .mode(Mode::Lines)
481            .name("Ranged");
482        let trace_sb10_i = Scatter::new(x_vec.clone(), sb10_i)
483            .mode(Mode::Markers)
484            .name("Iterated");
485
486        let mut plot_sb = Plot::new();
487        plot_sb.add_trace(trace_sb10_r);
488        plot_sb.add_trace(trace_sb10_i);
489
490        let layout_sb = Layout::new()
491            .title("Spherical Bessel functions of the first kind".into())
492            .x_axis(Axis::new().range(vec![x_start, x_end]))
493            .y_axis(Axis::new().range(vec![-0.5, 1.5]));
494        plot_sb.set_layout(layout_sb);
495
496        plot_sb.write_image("./sph_bessel_kind10.png", ImageFormat::PNG, 600, 400, 1.0);
497    }
498
499    #[test]
500    fn test_sph_bessel_kind1_ordn_arg_iterable() {
501        let x_start: f64 = 0.0;
502        let x_end: f64 = 10.0;
503        let x_step: f64 = 0.1;
504
505        let x_vec: Vec<f64> = range_to_vec(x_start, x_end, x_step);
506
507        let sb11_r: Vec<f64> = sph_bessel_kind1_ordern_arg_real_ranged(1, x_start, x_end, x_step);
508        let sb11_i: Vec<f64> = sph_bessel_kind1_ordern_arg_real_iterable(1, x_vec.clone());
509
510        let trace_sb11_r = Scatter::new(x_vec.clone(), sb11_r)
511            .mode(Mode::Lines)
512            .name("Ranged");
513        let trace_sb11_i = Scatter::new(x_vec.clone(), sb11_i)
514            .mode(Mode::Markers)
515            .name("Iterated");
516
517        let mut plot_sb = Plot::new();
518        plot_sb.add_trace(trace_sb11_r);
519        plot_sb.add_trace(trace_sb11_i);
520
521        let layout_sb = Layout::new()
522            .title("Spherical Bessel functions of the first kind".into())
523            .x_axis(Axis::new().range(vec![x_start, x_end]))
524            .y_axis(Axis::new().range(vec![-0.5, 1.5]));
525        plot_sb.set_layout(layout_sb);
526
527        plot_sb.write_image("./sph_bessel_kind11.png", ImageFormat::PNG, 600, 400, 1.0);
528    }
529
530    #[test]
531    fn test_sph_bessel_kind2_ord0_arg_iterable() {
532        let x_start: f64 = 0.0;
533        let x_end: f64 = 10.0;
534        let x_step: f64 = 0.1;
535
536        let x_vec: Vec<f64> = range_to_vec(x_start, x_end, x_step);
537
538        let sb20_r: Vec<f64> = sph_bessel_kind2_order0_arg_real_ranged(x_start, x_end, x_step);
539        let sb20_i: Vec<f64> = sph_bessel_kind2_order0_arg_real_iterable(x_vec.clone());
540
541        let trace_sb20_r = Scatter::new(x_vec.clone(), sb20_r)
542            .mode(Mode::Lines)
543            .name("Ranged");
544        let trace_sb20_i = Scatter::new(x_vec.clone(), sb20_i)
545            .mode(Mode::Markers)
546            .name("Iterated");
547
548        let mut plot_sb = Plot::new();
549        plot_sb.add_trace(trace_sb20_r);
550        plot_sb.add_trace(trace_sb20_i);
551
552        let layout_sb = Layout::new()
553            .title("Spherical Bessel functions of the second kind".into())
554            .x_axis(Axis::new().range(vec![x_start, x_end]))
555            .y_axis(Axis::new().range(vec![-2.5, 1.0]));
556        plot_sb.set_layout(layout_sb);
557
558        plot_sb.write_image("./sph_bessel_kind20.png", ImageFormat::PNG, 600, 400, 1.0);
559    }
560
561    #[test]
562    fn test_sph_bessel_kind2_ordn_arg_iterable() {
563        let x_start: f64 = 0.0;
564        let x_end: f64 = 10.0;
565        let x_step: f64 = 0.1;
566
567        let x_vec: Vec<f64> = range_to_vec(x_start, x_end, x_step);
568
569        let sb21_r: Vec<f64> = sph_bessel_kind2_ordern_arg_real_ranged(1, x_start, x_end, x_step);
570        let sb21_i: Vec<f64> = sph_bessel_kind2_ordern_arg_real_iterable(1, x_vec.clone());
571
572        let trace_sb21_r = Scatter::new(x_vec.clone(), sb21_r)
573            .mode(Mode::Lines)
574            .name("Ranged");
575        let trace_sb21_i = Scatter::new(x_vec.clone(), sb21_i)
576            .mode(Mode::Markers)
577            .name("Iterated");
578
579        let mut plot_sb = Plot::new();
580        plot_sb.add_trace(trace_sb21_r);
581        plot_sb.add_trace(trace_sb21_i);
582
583        let layout_sb = Layout::new()
584            .title("Spherical Bessel functions of the second kind".into())
585            .x_axis(Axis::new().range(vec![x_start, x_end]))
586            .y_axis(Axis::new().range(vec![-2.5, 1.0]));
587        plot_sb.set_layout(layout_sb);
588
589        plot_sb.write_image("./sph_bessel_kind21.png", ImageFormat::PNG, 600, 400, 1.0);
590    }
591
592}