Skip to main content

scirs2/
special.rs

1//! Python bindings for scirs2-special
2//!
3//! Provides special mathematical functions similar to scipy.special
4
5use pyo3::prelude::*;
6use scirs2_core::python::numpy_compat::{scirs_to_numpy_array1, Array1};
7use scirs2_numpy::{PyArray1, PyReadonlyArray1};
8
9// Import special functions from scirs2-special
10use scirs2_special::gamma::polygamma;
11use scirs2_special::{bessel, erf as erf_mod, gamma as gamma_fn};
12
13// =============================================================================
14// Gamma Functions
15// =============================================================================
16
17/// Gamma function
18#[pyfunction]
19fn gamma_py(x: f64) -> PyResult<f64> {
20    Ok(gamma_fn(x))
21}
22
23/// Log-gamma function
24#[pyfunction]
25fn lgamma_py(x: f64) -> PyResult<f64> {
26    Ok(scirs2_special::gamma::gammaln(x))
27}
28
29/// Digamma (psi) function
30#[pyfunction]
31fn digamma_py(x: f64) -> PyResult<f64> {
32    Ok(scirs2_special::gamma::digamma(x))
33}
34
35/// Beta function
36#[pyfunction]
37fn beta_py(a: f64, b: f64) -> PyResult<f64> {
38    Ok(scirs2_special::gamma::beta(a, b))
39}
40
41// =============================================================================
42// Bessel Functions
43// =============================================================================
44
45/// Bessel function of the first kind, J0
46#[pyfunction]
47fn j0_py(x: f64) -> PyResult<f64> {
48    Ok(bessel::j0(x))
49}
50
51/// Bessel function of the first kind, J1
52#[pyfunction]
53fn j1_py(x: f64) -> PyResult<f64> {
54    Ok(bessel::j1(x))
55}
56
57/// Bessel function of the first kind, Jn
58#[pyfunction]
59fn jn_py(n: i32, x: f64) -> PyResult<f64> {
60    Ok(bessel::jn(n, x))
61}
62
63/// Bessel function of the second kind, Y0
64#[pyfunction]
65fn y0_py(x: f64) -> PyResult<f64> {
66    Ok(bessel::y0(x))
67}
68
69/// Bessel function of the second kind, Y1
70#[pyfunction]
71fn y1_py(x: f64) -> PyResult<f64> {
72    Ok(bessel::y1(x))
73}
74
75/// Bessel function of the second kind, Yn
76#[pyfunction]
77fn yn_py(n: i32, x: f64) -> PyResult<f64> {
78    Ok(bessel::yn(n, x))
79}
80
81/// Modified Bessel function of the first kind, I0
82#[pyfunction]
83fn i0_py(x: f64) -> PyResult<f64> {
84    Ok(bessel::i0(x))
85}
86
87/// Modified Bessel function of the first kind, I1
88#[pyfunction]
89fn i1_py(x: f64) -> PyResult<f64> {
90    Ok(bessel::i1(x))
91}
92
93/// Modified Bessel function of the second kind, K0
94#[pyfunction]
95fn k0_py(x: f64) -> PyResult<f64> {
96    Ok(bessel::k0(x))
97}
98
99/// Modified Bessel function of the second kind, K1
100#[pyfunction]
101fn k1_py(x: f64) -> PyResult<f64> {
102    Ok(bessel::k1(x))
103}
104
105// =============================================================================
106// Error Functions
107// =============================================================================
108
109/// Error function
110#[pyfunction]
111fn erf_py(x: f64) -> PyResult<f64> {
112    Ok(erf_mod::erf(x))
113}
114
115/// Complementary error function
116#[pyfunction]
117fn erfc_py(x: f64) -> PyResult<f64> {
118    Ok(erf_mod::erfc(x))
119}
120
121/// Inverse error function
122#[pyfunction]
123fn erfinv_py(x: f64) -> PyResult<f64> {
124    Ok(erf_mod::erfinv(x))
125}
126
127/// Inverse complementary error function
128#[pyfunction]
129fn erfcinv_py(x: f64) -> PyResult<f64> {
130    Ok(erf_mod::erfcinv(x))
131}
132
133/// Scaled complementary error function
134#[pyfunction]
135fn erfcx_py(x: f64) -> PyResult<f64> {
136    Ok(erf_mod::erfcx(x))
137}
138
139/// Imaginary error function
140#[pyfunction]
141fn erfi_py(x: f64) -> PyResult<f64> {
142    Ok(erf_mod::erfi(x))
143}
144
145/// Dawson's integral
146#[pyfunction]
147fn dawsn_py(x: f64) -> PyResult<f64> {
148    Ok(erf_mod::dawsn(x))
149}
150
151// =============================================================================
152// Combinatorial Functions
153// =============================================================================
154
155/// Factorial function
156#[pyfunction]
157fn factorial_py(n: u32) -> PyResult<f64> {
158    scirs2_special::factorial(n)
159        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
160}
161
162/// Binomial coefficient (n choose k)
163#[pyfunction]
164fn comb_py(n: u32, k: u32) -> PyResult<f64> {
165    scirs2_special::comb(n, k)
166        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
167}
168
169/// Permutations (n permute k)
170#[pyfunction]
171fn perm_py(n: u32, k: u32) -> PyResult<f64> {
172    scirs2_special::perm(n, k)
173        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
174}
175
176// =============================================================================
177// Elliptic Integrals
178// =============================================================================
179
180/// Complete elliptic integral of the first kind
181#[pyfunction]
182fn ellipk_py(m: f64) -> PyResult<f64> {
183    Ok(scirs2_special::elliptic_k(m))
184}
185
186/// Complete elliptic integral of the second kind
187#[pyfunction]
188fn ellipe_py(m: f64) -> PyResult<f64> {
189    Ok(scirs2_special::elliptic_e(m))
190}
191
192/// Incomplete elliptic integral of the first kind
193#[pyfunction]
194fn ellipkinc_py(phi: f64, m: f64) -> PyResult<f64> {
195    Ok(scirs2_special::elliptic_f(phi, m))
196}
197
198/// Incomplete elliptic integral of the second kind
199#[pyfunction]
200fn ellipeinc_py(phi: f64, m: f64) -> PyResult<f64> {
201    Ok(scirs2_special::elliptic_e_inc(phi, m))
202}
203
204// =============================================================================
205// Polygamma Function
206// =============================================================================
207
208/// Polygamma function: the n-th derivative of the digamma function
209#[pyfunction]
210fn polygamma_py(n: u32, x: f64) -> PyResult<f64> {
211    Ok(polygamma(n, x))
212}
213
214// =============================================================================
215// Zeta Functions
216// =============================================================================
217
218/// Riemann zeta function
219#[pyfunction]
220fn zeta_py(s: f64) -> PyResult<f64> {
221    scirs2_special::zeta(s).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
222}
223
224/// Hurwitz zeta function
225#[pyfunction]
226fn hurwitz_zeta_py(s: f64, q: f64) -> PyResult<f64> {
227    scirs2_special::hurwitz_zeta(s, q)
228        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
229}
230
231/// Riemann zeta minus 1: zeta(s) - 1
232#[pyfunction]
233fn zetac_py(s: f64) -> PyResult<f64> {
234    scirs2_special::zetac(s).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
235}
236
237// =============================================================================
238// Hypergeometric Functions
239// =============================================================================
240
241/// Confluent hypergeometric function 0F1
242#[pyfunction]
243fn hyp0f1_py(v: f64, z: f64) -> PyResult<f64> {
244    scirs2_special::hyp0f1(v, z)
245        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
246}
247
248/// Confluent hypergeometric function 1F1 (Kummer's function)
249#[pyfunction]
250fn hyp1f1_py(a: f64, b: f64, z: f64) -> PyResult<f64> {
251    scirs2_special::hyp1f1(a, b, z)
252        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
253}
254
255/// Gauss hypergeometric function 2F1
256#[pyfunction]
257fn hyp2f1_py(a: f64, b: f64, c: f64, z: f64) -> PyResult<f64> {
258    scirs2_special::hyp2f1(a, b, c, z)
259        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
260}
261
262/// Tricomi's confluent hypergeometric function U(a, b, x)
263#[pyfunction]
264fn hyperu_py(a: f64, b: f64, x: f64) -> PyResult<f64> {
265    scirs2_special::hyperu(a, b, x)
266        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
267}
268
269// =============================================================================
270// Airy Functions
271// =============================================================================
272
273/// Airy function Ai(x)
274#[pyfunction]
275fn airy_ai_py(x: f64) -> PyResult<f64> {
276    Ok(scirs2_special::ai(x))
277}
278
279/// Derivative of Airy function Ai'(x)
280#[pyfunction]
281fn airy_aip_py(x: f64) -> PyResult<f64> {
282    Ok(scirs2_special::aip(x))
283}
284
285/// Airy function Bi(x)
286#[pyfunction]
287fn airy_bi_py(x: f64) -> PyResult<f64> {
288    Ok(scirs2_special::bi(x))
289}
290
291/// Derivative of Airy function Bi'(x)
292#[pyfunction]
293fn airy_bip_py(x: f64) -> PyResult<f64> {
294    Ok(scirs2_special::bip(x))
295}
296
297// =============================================================================
298// Trigonometric and Exponential Integrals
299// =============================================================================
300
301/// Sine integral Si(x)
302#[pyfunction]
303fn sici_si_py(x: f64) -> PyResult<f64> {
304    scirs2_special::si(x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
305}
306
307/// Cosine integral Ci(x)
308#[pyfunction]
309fn sici_ci_py(x: f64) -> PyResult<f64> {
310    scirs2_special::ci(x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
311}
312
313/// Hyperbolic sine integral Shi(x)
314#[pyfunction]
315fn shichi_shi_py(x: f64) -> PyResult<f64> {
316    scirs2_special::shi(x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
317}
318
319/// Hyperbolic cosine integral Chi(x)
320#[pyfunction]
321fn shichi_chi_py(x: f64) -> PyResult<f64> {
322    scirs2_special::chi(x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
323}
324
325// =============================================================================
326// Incomplete Beta Function
327// =============================================================================
328
329/// Regularized incomplete beta function I_x(a, b)
330#[pyfunction]
331fn betainc_py(a: f64, b: f64, x: f64) -> PyResult<f64> {
332    scirs2_special::gamma::betainc_regularized(a, b, x)
333        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
334}
335
336/// Inverse of regularized incomplete beta function
337#[pyfunction]
338fn betaincinv_py(a: f64, b: f64, p: f64) -> PyResult<f64> {
339    scirs2_special::gamma::betaincinv(a, b, p)
340        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
341}
342
343// =============================================================================
344// Vectorized versions (for arrays)
345// =============================================================================
346
347/// Vectorized gamma function
348#[pyfunction]
349fn gamma_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
350    let input = x.as_array();
351    let output: Vec<f64> = input.iter().map(|&v| gamma_fn(v)).collect();
352    scirs_to_numpy_array1(Array1::from_vec(output), py)
353}
354
355/// Vectorized error function
356#[pyfunction]
357fn erf_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
358    let input = x.as_array();
359    let output: Vec<f64> = input.iter().map(|&v| erf_mod::erf(v)).collect();
360    scirs_to_numpy_array1(Array1::from_vec(output), py)
361}
362
363/// Vectorized J0 Bessel function
364#[pyfunction]
365fn j0_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
366    let input = x.as_array();
367    let output: Vec<f64> = input.iter().map(|&v| bessel::j0(v)).collect();
368    scirs_to_numpy_array1(Array1::from_vec(output), py)
369}
370
371/// Vectorized lgamma function
372#[pyfunction]
373fn lgamma_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
374    let input = x.as_array();
375    let output: Vec<f64> = input
376        .iter()
377        .map(|&v| scirs2_special::gamma::gammaln(v))
378        .collect();
379    scirs_to_numpy_array1(Array1::from_vec(output), py)
380}
381
382/// Vectorized erfc function
383#[pyfunction]
384fn erfc_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
385    let input = x.as_array();
386    let output: Vec<f64> = input.iter().map(|&v| erf_mod::erfc(v)).collect();
387    scirs_to_numpy_array1(Array1::from_vec(output), py)
388}
389
390/// Vectorized digamma function
391#[pyfunction]
392fn digamma_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
393    let input = x.as_array();
394    let output: Vec<f64> = input
395        .iter()
396        .map(|&v| scirs2_special::gamma::digamma(v))
397        .collect();
398    scirs_to_numpy_array1(Array1::from_vec(output), py)
399}
400
401/// Vectorized J1 Bessel function
402#[pyfunction]
403fn j1_array_py(py: Python, x: PyReadonlyArray1<f64>) -> PyResult<Py<PyArray1<f64>>> {
404    let input = x.as_array();
405    let output: Vec<f64> = input.iter().map(|&v| bessel::j1(v)).collect();
406    scirs_to_numpy_array1(Array1::from_vec(output), py)
407}
408
409/// Python module registration
410pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
411    // Gamma functions
412    m.add_function(wrap_pyfunction!(gamma_py, m)?)?;
413    m.add_function(wrap_pyfunction!(lgamma_py, m)?)?;
414    m.add_function(wrap_pyfunction!(digamma_py, m)?)?;
415    m.add_function(wrap_pyfunction!(beta_py, m)?)?;
416
417    // Bessel functions
418    m.add_function(wrap_pyfunction!(j0_py, m)?)?;
419    m.add_function(wrap_pyfunction!(j1_py, m)?)?;
420    m.add_function(wrap_pyfunction!(jn_py, m)?)?;
421    m.add_function(wrap_pyfunction!(y0_py, m)?)?;
422    m.add_function(wrap_pyfunction!(y1_py, m)?)?;
423    m.add_function(wrap_pyfunction!(yn_py, m)?)?;
424    m.add_function(wrap_pyfunction!(i0_py, m)?)?;
425    m.add_function(wrap_pyfunction!(i1_py, m)?)?;
426    m.add_function(wrap_pyfunction!(k0_py, m)?)?;
427    m.add_function(wrap_pyfunction!(k1_py, m)?)?;
428
429    // Error functions
430    m.add_function(wrap_pyfunction!(erf_py, m)?)?;
431    m.add_function(wrap_pyfunction!(erfc_py, m)?)?;
432    m.add_function(wrap_pyfunction!(erfinv_py, m)?)?;
433    m.add_function(wrap_pyfunction!(erfcinv_py, m)?)?;
434    m.add_function(wrap_pyfunction!(erfcx_py, m)?)?;
435    m.add_function(wrap_pyfunction!(erfi_py, m)?)?;
436    m.add_function(wrap_pyfunction!(dawsn_py, m)?)?;
437
438    // Combinatorial functions
439    m.add_function(wrap_pyfunction!(factorial_py, m)?)?;
440    m.add_function(wrap_pyfunction!(comb_py, m)?)?;
441    m.add_function(wrap_pyfunction!(perm_py, m)?)?;
442
443    // Elliptic integrals
444    m.add_function(wrap_pyfunction!(ellipk_py, m)?)?;
445    m.add_function(wrap_pyfunction!(ellipe_py, m)?)?;
446    m.add_function(wrap_pyfunction!(ellipkinc_py, m)?)?;
447    m.add_function(wrap_pyfunction!(ellipeinc_py, m)?)?;
448
449    // Polygamma
450    m.add_function(wrap_pyfunction!(polygamma_py, m)?)?;
451
452    // Zeta functions
453    m.add_function(wrap_pyfunction!(zeta_py, m)?)?;
454    m.add_function(wrap_pyfunction!(hurwitz_zeta_py, m)?)?;
455    m.add_function(wrap_pyfunction!(zetac_py, m)?)?;
456
457    // Hypergeometric functions
458    m.add_function(wrap_pyfunction!(hyp0f1_py, m)?)?;
459    m.add_function(wrap_pyfunction!(hyp1f1_py, m)?)?;
460    m.add_function(wrap_pyfunction!(hyp2f1_py, m)?)?;
461    m.add_function(wrap_pyfunction!(hyperu_py, m)?)?;
462
463    // Airy functions
464    m.add_function(wrap_pyfunction!(airy_ai_py, m)?)?;
465    m.add_function(wrap_pyfunction!(airy_aip_py, m)?)?;
466    m.add_function(wrap_pyfunction!(airy_bi_py, m)?)?;
467    m.add_function(wrap_pyfunction!(airy_bip_py, m)?)?;
468
469    // Trig/exponential integrals
470    m.add_function(wrap_pyfunction!(sici_si_py, m)?)?;
471    m.add_function(wrap_pyfunction!(sici_ci_py, m)?)?;
472    m.add_function(wrap_pyfunction!(shichi_shi_py, m)?)?;
473    m.add_function(wrap_pyfunction!(shichi_chi_py, m)?)?;
474
475    // Incomplete beta
476    m.add_function(wrap_pyfunction!(betainc_py, m)?)?;
477    m.add_function(wrap_pyfunction!(betaincinv_py, m)?)?;
478
479    // Vectorized versions
480    m.add_function(wrap_pyfunction!(gamma_array_py, m)?)?;
481    m.add_function(wrap_pyfunction!(lgamma_array_py, m)?)?;
482    m.add_function(wrap_pyfunction!(erf_array_py, m)?)?;
483    m.add_function(wrap_pyfunction!(erfc_array_py, m)?)?;
484    m.add_function(wrap_pyfunction!(digamma_array_py, m)?)?;
485    m.add_function(wrap_pyfunction!(j0_array_py, m)?)?;
486    m.add_function(wrap_pyfunction!(j1_array_py, m)?)?;
487
488    Ok(())
489}