use num_bigint::BigInt;
use ocas_domain::Integer;
use ocas_domain::number_theory::crt::crt_many;
use ocas_domain::number_theory::dlog::{dlog_bsgs, dlog_pohlig_hellman};
use ocas_domain::number_theory::factor::factor_integer;
use ocas_domain::number_theory::functions::{
divisor_sigma, divisor_tau, euler_phi, liouville_lambda, moebius_mu,
};
use ocas_domain::number_theory::primes::{is_prime_bpsw, is_prime_u64};
use ocas_domain::number_theory::{jacobi, next_prime};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
fn to_integer(v: &Bound<'_, PyAny>) -> PyResult<Integer> {
let s = v.str()?;
let s = s.to_str()?;
s.parse::<BigInt>()
.map(Integer::from)
.map_err(|_| PyValueError::new_err(format!("expected an integer, got {s:?}")))
}
fn to_py_int(py: Python<'_>, n: &Integer) -> PyResult<Py<PyAny>> {
let s = n.to_string();
let builtins = PyModule::import(py, "builtins")?;
let int_cls = builtins.getattr("int")?;
Ok(int_cls.call1((s,))?.unbind())
}
#[pyfunction]
#[pyo3(name = "factorint")]
pub fn py_factorint(n: &Bound<'_, PyAny>) -> PyResult<Vec<(String, u32)>> {
let n = to_integer(n)?;
let mut out: Vec<(String, u32)> = Vec::new();
if n.is_negative() {
out.push(("-1".to_string(), 1));
}
out.extend(
factor_integer(&n)
.into_iter()
.map(|(p, e)| (p.to_string(), e)),
);
Ok(out)
}
#[pyfunction]
#[pyo3(name = "isprime")]
pub fn py_isprime(n: &Bound<'_, PyAny>) -> PyResult<bool> {
let n = to_integer(n)?;
Ok(is_prime_bpsw(&n))
}
#[pyfunction]
#[pyo3(name = "nextprime")]
pub fn py_nextprime(py: Python<'_>, n: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
let n = to_integer(n)?;
to_py_int(py, &next_prime(&n))
}
#[pyfunction]
#[pyo3(name = "discrete_log")]
pub fn py_discrete_log(
py: Python<'_>,
p: &Bound<'_, PyAny>,
base: &Bound<'_, PyAny>,
target: &Bound<'_, PyAny>,
) -> PyResult<Py<PyAny>> {
let p = to_integer(p)?;
let base = to_integer(base)?;
let target = to_integer(target)?;
let x = if is_prime_bpsw(&p) {
dlog_pohlig_hellman(&base, &target, &p)
} else {
dlog_bsgs(&base, &target, &p)
};
match x {
Some(x) => to_py_int(py, &x),
None => Err(PyValueError::new_err(
"no discrete logarithm exists for these inputs",
)),
}
}
#[pyfunction]
#[pyo3(name = "crt")]
pub fn py_crt(
py: Python<'_>,
moduli: &Bound<'_, PyAny>,
residues: &Bound<'_, PyAny>,
) -> PyResult<(Py<PyAny>, Py<PyAny>)> {
let moduli = moduli
.cast::<pyo3::types::PySequence>()
.map_err(|_| PyValueError::new_err("moduli must be a sequence"))?;
let residues = residues
.cast::<pyo3::types::PySequence>()
.map_err(|_| PyValueError::new_err("residues must be a sequence"))?;
if moduli.len()? != residues.len()? {
return Err(PyValueError::new_err(
"moduli and residues must have the same length",
));
}
let mut cs = Vec::with_capacity(moduli.len()?);
for i in 0..moduli.len()? {
let m = moduli.get_item(i)?;
let r = residues.get_item(i)?;
cs.push((to_integer(&r)?, to_integer(&m)?));
}
let (r, m) = crt_many(&cs).ok_or_else(|| PyValueError::new_err("inconsistent CRT system"))?;
Ok((to_py_int(py, &r)?, to_py_int(py, &m)?))
}
#[pyfunction]
#[pyo3(name = "jacobi_symbol")]
pub fn py_jacobi_symbol(a: &Bound<'_, PyAny>, n: &Bound<'_, PyAny>) -> PyResult<i8> {
let a = to_integer(a)?;
let n = to_integer(n)?;
if n.is_zero() || n.is_negative() || n.is_even() {
return Err(PyValueError::new_err("n must be a positive odd integer"));
}
Ok(jacobi(&a, &n))
}
#[pyfunction]
#[pyo3(name = "totient")]
pub fn py_totient(py: Python<'_>, n: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
let n = to_integer(n)?;
to_py_int(py, &euler_phi(&n))
}
#[pyfunction]
#[pyo3(name = "mobius")]
pub fn py_mobius(n: &Bound<'_, PyAny>) -> PyResult<i8> {
let n = to_integer(n)?;
Ok(moebius_mu(&n))
}
#[pyfunction]
#[pyo3(name = "divisor_count")]
pub fn py_divisor_count(py: Python<'_>, n: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
let n = to_integer(n)?;
to_py_int(py, &divisor_tau(&n))
}
#[pyfunction]
#[pyo3(name = "divisor_sigma", signature = (n, k=1))]
pub fn py_divisor_sigma(py: Python<'_>, n: &Bound<'_, PyAny>, k: u32) -> PyResult<Py<PyAny>> {
let n = to_integer(n)?;
to_py_int(py, &divisor_sigma(&n, k))
}
#[pyfunction]
#[pyo3(name = "liouville_lambda")]
pub fn py_liouville_lambda(n: &Bound<'_, PyAny>) -> PyResult<i8> {
let n = to_integer(n)?;
Ok(liouville_lambda(&n))
}
#[pyfunction]
#[pyo3(name = "isprime_u64")]
pub fn py_isprime_u64(n: u64) -> bool {
is_prime_u64(n)
}