Skip to content

Commit f579018

Browse files
finish implementing python bindings and start adding tests
1 parent d7d2904 commit f579018

File tree

2 files changed

+107
-7
lines changed

2 files changed

+107
-7
lines changed

src/python.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use pyo3::types::PyAny;
2020
use pyo3::types::PyBytes;
2121
use pyo3::types::PyInt;
2222
use pyo3::types::PyType;
23+
use pyo3::FromPy;
2324
use pyo3::PyNativeType;
2425
use pyo3::PyNumberProtocol;
2526
use pyo3::PyObjectProtocol;
@@ -51,6 +52,12 @@ impl ToPyObject for PyBigInt {
5152
}
5253
}
5354

55+
impl FromPy<PyBigInt> for PyObject {
56+
fn from_py(value: PyBigInt, py: Python) -> Self {
57+
value.to_object(py)
58+
}
59+
}
60+
5461
impl FromPyObject<'_> for PyBigInt {
5562
fn extract(ob: &PyAny) -> PyResult<Self> {
5663
let value = ob.downcast_ref::<PyInt>()?;
@@ -97,17 +104,69 @@ impl FromPyObject<'_> for RealAlgebraicNumberPy {
97104
#[pymethods(PyObjectProtocol, PyNumberProtocol)]
98105
impl RealAlgebraicNumberPy {
99106
#[new]
100-
fn pynew(obj: &PyRawObject, value: RealAlgebraicNumberPy) -> PyResult<()> {
101-
obj.init(value);
102-
Ok(())
107+
fn pynew(obj: &PyRawObject, value: Option<RealAlgebraicNumberPy>) {
108+
obj.init(value.unwrap_or_else(|| RealAlgebraicNumberPy {
109+
value: RealAlgebraicNumber::zero().into(),
110+
}));
111+
}
112+
fn __trunc__(&self) -> PyBigInt {
113+
let gil = Python::acquire_gil();
114+
let py = gil.python();
115+
py.allow_threads(|| PyBigInt(self.value.to_integer_trunc()))
116+
}
117+
fn __floor__(&self) -> PyBigInt {
118+
let gil = Python::acquire_gil();
119+
let py = gil.python();
120+
py.allow_threads(|| PyBigInt(self.value.to_integer_floor()))
121+
}
122+
fn __ceil__(&self) -> PyBigInt {
123+
let gil = Python::acquire_gil();
124+
let py = gil.python();
125+
py.allow_threads(|| PyBigInt(self.value.to_integer_ceil()))
126+
}
127+
fn to_integer(&self) -> Option<PyBigInt> {
128+
self.value.to_integer().map(PyBigInt)
129+
}
130+
fn to_rational(&self) -> Option<(PyBigInt, PyBigInt)> {
131+
self.value.to_rational().map(|value| {
132+
let (numer, denom) = value.into();
133+
(PyBigInt(numer), PyBigInt(denom))
134+
})
135+
}
136+
#[getter]
137+
fn minimal_polynomial(&self) -> Vec<PyBigInt> {
138+
self.value
139+
.minimal_polynomial()
140+
.iter()
141+
.map(PyBigInt)
142+
.collect()
143+
}
144+
#[getter]
145+
fn degree(&self) -> usize {
146+
self.value.degree()
147+
}
148+
fn is_rational(&self) -> bool {
149+
self.value.is_rational()
150+
}
151+
fn is_integer(&self) -> bool {
152+
self.value.is_integer()
153+
}
154+
fn recip(&self) -> PyResult<RealAlgebraicNumberPy> {
155+
Python::acquire_gil()
156+
.python()
157+
.allow_threads(|| {
158+
Some(RealAlgebraicNumberPy {
159+
value: self.value.checked_recip()?.into(),
160+
})
161+
})
162+
.ok_or_else(get_div_by_zero_error)
103163
}
104-
// FIXME: implement rest of methods
105164
}
106165

107166
#[pyproto]
108167
impl PyObjectProtocol for RealAlgebraicNumberPy {
109168
fn __repr__(&self) -> PyResult<String> {
110-
Ok(format!("{:?}", self.value))
169+
Ok(format!("<{:?}>", self.value))
111170
}
112171
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
113172
let py = other.py();
@@ -123,6 +182,10 @@ impl PyObjectProtocol for RealAlgebraicNumberPy {
123182
}
124183
}
125184

185+
fn get_div_by_zero_error() -> PyErr {
186+
ZeroDivisionError::py_err("can't divide RealAlgebraicNumber by zero")
187+
}
188+
126189
#[pyproto]
127190
impl PyNumberProtocol for RealAlgebraicNumberPy {
128191
fn __add__(lhs: &PyAny, rhs: RealAlgebraicNumberPy) -> PyResult<RealAlgebraicNumberPy> {
@@ -156,7 +219,7 @@ impl PyNumberProtocol for RealAlgebraicNumberPy {
156219
Arc::make_mut(&mut lhs.value).checked_exact_div_assign(&*rhs.value)?;
157220
Ok(lhs)
158221
})
159-
.map_err(|()| ZeroDivisionError::py_err("can't divide RealAlgebraicNumber by zero"))
222+
.map_err(|()| get_div_by_zero_error())
160223
}
161224
fn __pow__(
162225
lhs: RealAlgebraicNumberPy,
@@ -204,7 +267,6 @@ impl PyNumberProtocol for RealAlgebraicNumberPy {
204267

205268
#[pymodule]
206269
fn algebraics(_py: Python, m: &PyModule) -> PyResult<()> {
207-
// FIXME: add module members
208270
m.add_class::<RealAlgebraicNumberPy>()?;
209271
Ok(())
210272
}

tests/test_real_algebraic_number.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# SPDX-License-Identifier: LGPL-2.1-or-later
2+
# See Notices.txt for copyright information
3+
4+
from algebraics import RealAlgebraicNumber
5+
import unittest
6+
7+
8+
class TestRealAlgebraicNumber(unittest.TestCase):
9+
def test_construct(self):
10+
self.assertEqual(repr(RealAlgebraicNumber()),
11+
"<RealAlgebraicNumber { minimal_polynomial: 0 + 1*X, "
12+
"interval: DyadicFractionInterval { "
13+
"lower_bound_numer: 0, upper_bound_numer: 0, "
14+
"log2_denom: 0 } }>")
15+
self.assertEqual(repr(RealAlgebraicNumber(42)),
16+
"<RealAlgebraicNumber { "
17+
"minimal_polynomial: -42 + 1*X, "
18+
"interval: DyadicFractionInterval { "
19+
"lower_bound_numer: 42, upper_bound_numer: 42, "
20+
"log2_denom: 0 } }>")
21+
self.assertEqual(repr(RealAlgebraicNumber(-5)),
22+
"<RealAlgebraicNumber { "
23+
"minimal_polynomial: 5 + 1*X, "
24+
"interval: DyadicFractionInterval { "
25+
"lower_bound_numer: -5, upper_bound_numer: -5, "
26+
"log2_denom: 0 } }>")
27+
self.assertEqual(repr(RealAlgebraicNumber(RealAlgebraicNumber(-5))),
28+
"<RealAlgebraicNumber { "
29+
"minimal_polynomial: 5 + 1*X, "
30+
"interval: DyadicFractionInterval { "
31+
"lower_bound_numer: -5, upper_bound_numer: -5, "
32+
"log2_denom: 0 } }>")
33+
34+
# FIXME: add more tests
35+
36+
37+
if __name__ == '__main__':
38+
unittest.main()

0 commit comments

Comments
 (0)