Skip to main content

ocas_py/
solve.rs

1//! Python `solve` module — linear and Diophantine equation solvers.
2
3use ocas_calc::solve::{
4    DiophantineSolution, SolveError, solve_diophantine as rs_diophantine,
5    solve_linear_integer as rs_linear_integer, solve_linear_rational as rs_linear_rational,
6};
7use pyo3::exceptions::PyValueError;
8use pyo3::prelude::*;
9
10fn map_solve_err(e: SolveError) -> PyErr {
11    PyValueError::new_err(e.to_string())
12}
13
14/// Solve the linear system `a * x = b` over the rationals.
15///
16/// Each element of `a` is a row (list of ints). Returns a list of
17/// `(numerator, denominator)` tuples, or raises `ValueError` if no
18/// unique solution exists.
19#[pyfunction]
20#[pyo3(name = "solve_linear_rational")]
21pub fn py_solve_linear_rational(a: Vec<Vec<i64>>, b: Vec<i64>) -> PyResult<Vec<(i64, i64)>> {
22    rs_linear_rational(&a, &b).map_err(map_solve_err)
23}
24
25/// Solve the linear system `a * x = b` over the integers.
26///
27/// Returns a list of integer solutions, or raises `ValueError` if no
28/// integer solution exists.
29#[pyfunction]
30#[pyo3(name = "solve_linear_integer")]
31pub fn py_solve_linear_integer(a: Vec<Vec<i64>>, b: Vec<i64>) -> PyResult<Vec<i64>> {
32    rs_linear_integer(&a, &b).map_err(map_solve_err)
33}
34
35/// A solution to the Diophantine equation `a*x + b*y = c`.
36#[pyclass(name = "DiophantineSolution")]
37pub struct PyDiophantineSolution {
38    /// Particular solution `(x0, y0)`.
39    #[pyo3(get)]
40    pub particular: (i64, i64),
41    /// Homogeneous direction `(tx, ty)`; general solution is
42    /// `(x0 + k*tx, y0 + k*ty)` for any integer `k`.
43    #[pyo3(get)]
44    pub general: (i64, i64),
45}
46
47impl From<DiophantineSolution> for PyDiophantineSolution {
48    fn from(s: DiophantineSolution) -> Self {
49        PyDiophantineSolution {
50            particular: s.particular,
51            general: s.general,
52        }
53    }
54}
55
56/// Solve the linear Diophantine equation `a*x + b*y = c`.
57///
58/// Returns `None` if no integer solution exists.
59#[pyfunction]
60#[pyo3(name = "solve_diophantine")]
61pub fn py_solve_diophantine(a: i64, b: i64, c: i64) -> Option<PyDiophantineSolution> {
62    rs_diophantine(a, b, c).map(Into::into)
63}