Skip to main content

ocas_py/
domain.rs

1//! Python `Domain` classes — coefficient-domain selectors.
2//!
3//! Provides [`PyIntegerDomain`], [`PyRationalDomain`], and [`PyFiniteField`]
4//! wrapper classes. These mirror oCAS's Rust-side domain objects and are used
5//! by the Python `Polynomial` and `Matrix` classes to select the coefficient
6//! ring.
7
8use num_bigint::BigInt;
9use pyo3::exceptions::PyValueError;
10use pyo3::prelude::*;
11
12/// Selector enum bridging the three supported Python domains to their Rust
13/// counterparts. Used internally by `Polynomial` and `Matrix`.
14pub(crate) enum DomainKind {
15    Integer,
16    Rational,
17    FiniteField(BigInt),
18}
19
20impl DomainKind {
21    /// Parse a Python object into a [`DomainKind`].
22    ///
23    /// Accepts either a domain-name string (`"integer"`, `"int"`, `"Z"`,
24    /// `"rational"`, `"rat"`, `"Q"`) or a `FiniteField` instance.
25    pub(crate) fn from_py(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
26        if let Ok(s) = obj.extract::<String>() {
27            match s.as_str() {
28                "integer" | "int" | "Z" => Ok(DomainKind::Integer),
29                "rational" | "rat" | "Q" => Ok(DomainKind::Rational),
30                other => Err(PyValueError::new_err(format!(
31                    "unknown domain string: {other:?} (expected one of \
32                     'integer'/'int'/'Z', 'rational'/'rat'/'Q', or a FiniteField)"
33                ))),
34            }
35        } else if let Ok(fq) = obj.extract::<PyRef<'_, PyFiniteField>>() {
36            Ok(DomainKind::FiniteField(fq.modulus.clone()))
37        } else {
38            Err(PyValueError::new_err(
39                "domain must be a string ('integer'/'rational') or a FiniteField instance",
40            ))
41        }
42    }
43}
44
45/// The integer domain ℤ.
46///
47/// ```python
48/// from ocas import IntegerDomain
49/// d = IntegerDomain()
50/// print(repr(d))  # IntegerDomain()
51/// ```
52#[pyclass(name = "IntegerDomain", skip_from_py_object)]
53#[derive(Clone)]
54pub struct PyIntegerDomain;
55
56#[pymethods]
57impl PyIntegerDomain {
58    #[new]
59    fn new() -> Self {
60        PyIntegerDomain
61    }
62
63    fn __repr__(&self) -> String {
64        "IntegerDomain()".to_string()
65    }
66}
67
68/// The rational number domain ℚ.
69///
70/// ```python
71/// from ocas import RationalDomain
72/// d = RationalDomain()
73/// ```
74#[pyclass(name = "RationalDomain", skip_from_py_object)]
75#[derive(Clone)]
76pub struct PyRationalDomain;
77
78#[pymethods]
79impl PyRationalDomain {
80    #[new]
81    fn new() -> Self {
82        PyRationalDomain
83    }
84
85    fn __repr__(&self) -> String {
86        "RationalDomain()".to_string()
87    }
88}
89
90/// A finite field GF(p) for a prime modulus `p`.
91///
92/// ```python
93/// from ocas import FiniteField
94/// gf5 = FiniteField(5)
95/// print(repr(gf5))  # FiniteField(5)
96/// ```
97#[pyclass(name = "FiniteField", from_py_object)]
98#[derive(Clone)]
99pub struct PyFiniteField {
100    pub(crate) modulus: BigInt,
101}
102
103#[pymethods]
104impl PyFiniteField {
105    /// Create `GF(p)` with prime modulus `p` (an int ≥ 2).
106    #[new]
107    fn new(modulus: i64) -> PyResult<Self> {
108        if modulus < 2 {
109            return Err(PyValueError::new_err(format!(
110                "finite-field modulus must be a prime ≥ 2, got {modulus}"
111            )));
112        }
113        Ok(PyFiniteField {
114            modulus: BigInt::from(modulus),
115        })
116    }
117
118    /// Return the prime modulus as a decimal string.
119    #[getter]
120    fn modulus(&self) -> String {
121        self.modulus.to_string()
122    }
123
124    fn __repr__(&self) -> String {
125        format!("FiniteField({})", self.modulus)
126    }
127}