From bf2329557ee80c61c6c7c635b4162b47fd2199fc Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 31 May 2021 20:42:13 +0200 Subject: [PATCH 1/8] move num_bigint_impl to own module --- src/lib.rs | 7 ++ src/num_bigint.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++ src/types/num.rs | 236 ---------------------------------------------- 3 files changed, 242 insertions(+), 236 deletions(-) create mode 100644 src/num_bigint.rs diff --git a/src/lib.rs b/src/lib.rs index b47610babe0..26ccdd951d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -298,6 +298,13 @@ mod python; pub mod type_object; pub mod types; +#[cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] +#[cfg_attr( + docsrs, + doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))) +)] +pub mod num_bigint; + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg(feature = "serde")] pub mod serde; diff --git a/src/num_bigint.rs b/src/num_bigint.rs new file mode 100644 index 00000000000..e1a3bf23d36 --- /dev/null +++ b/src/num_bigint.rs @@ -0,0 +1,235 @@ +#![cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] + + + + + +use crate::{ + err, ffi, types::*, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyErr, PyNativeType, + PyObject, PyResult, Python, ToPyObject, +}; + +use num_bigint::{BigInt, BigUint}; +use std::os::raw::{c_int, c_uchar}; + +#[cfg(not(all(windows, PyPy)))] +unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> { + err::error_on_minusone( + ob.py(), + ffi::_PyLong_AsByteArray( + ob.as_ptr() as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + buffer.len(), + 1, + is_signed, + ), + ) +} + +macro_rules! bigint_conversion { + ($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => { + impl ToPyObject for $rust_ty { + fn to_object(&self, py: Python) -> PyObject { + unsafe { + let bytes = $to_bytes(self); + let obj = ffi::_PyLong_FromByteArray( + bytes.as_ptr() as *const c_uchar, + bytes.len(), + 1, + $is_signed, + ); + PyObject::from_owned_ptr(py, obj) + } + } + } + impl IntoPy for $rust_ty { + fn into_py(self, py: Python) -> PyObject { + self.to_object(py) + } + } + impl<'source> FromPyObject<'source> for $rust_ty { + fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> { + let py = ob.py(); + unsafe { + let num = ffi::PyNumber_Index(ob.as_ptr()); + if num.is_null() { + return Err(PyErr::fetch(py)); + } + let n_bits = ffi::_PyLong_NumBits(num); + let n_bytes = if n_bits < 0 { + return Err(PyErr::fetch(py)); + } else if n_bits == 0 { + 0 + } else { + (n_bits as usize - 1 + $is_signed) / 8 + 1 + }; + let num: Py = Py::from_owned_ptr(py, num); + if n_bytes <= 128 { + let mut buffer = [0; 128]; + extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?; + Ok($from_bytes(&buffer[..n_bytes])) + } else { + let mut buffer = vec![0; n_bytes]; + extract(num.as_ref(py), &mut buffer, $is_signed)?; + Ok($from_bytes(&buffer)) + } + } + } + } + }; +} +bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le); +bigint_conversion!( + BigInt, + 1, + BigInt::to_signed_bytes_le, + BigInt::from_signed_bytes_le +); + +#[cfg(test)] +mod test { + use super::*; + use crate::types::{PyDict, PyModule}; + use indoc::indoc; + + fn python_fib(py: Python) -> &PyModule { + let fib_code = indoc!( + r#" + def fib(n): + f0, f1 = 0, 1 + for _ in range(n): + f0, f1 = f1, f0 + f1 + return f0 + + def fib_neg(n): + return -fib(n) + "# + ); + PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap() + } + + fn rust_fib(n: usize) -> T + where + T: From, + for<'a> &'a T: std::ops::Add, + { + let mut f0: T = T::from(0); + let mut f1: T = T::from(1); + for _ in 0..n { + let f2 = &f0 + &f1; + f0 = std::mem::replace(&mut f1, f2); + } + f0 + } + + #[test] + fn convert_biguint() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let rs_result: BigUint = rust_fib(400); + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigUint -> Python Long conversion is correct + py.run("assert fib.fib(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigUint conversion is correct if N is small + let py_result: BigUint = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigUint conversion is correct if N is large + let rs_result: BigUint = rust_fib(2000); + let py_result: BigUint = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + } + + #[test] + fn convert_bigint() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let rs_result = rust_fib::(400) * -1; + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigInt -> Python Long conversion is correct + py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigInt conversion is correct if N is small + let py_result: BigInt = + FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigInt conversion is correct if N is large + let rs_result = rust_fib::(2000) * -1; + let py_result: BigInt = + FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + } + + fn python_index_class(py: Python) -> &PyModule { + let index_code = indoc!( + r#" + class C: + def __init__(self, x): + self.x = x + def __index__(self): + return self.x + "# + ); + PyModule::from_code(py, index_code, "index.py", "index").unwrap() + } + + #[test] + fn convert_index_class() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let index = python_index_class(py); + let locals = PyDict::new(py); + locals.set_item("index", index).unwrap(); + let ob = py.eval("index.C(10)", None, Some(locals)).unwrap(); + let _: BigInt = FromPyObject::extract(ob).unwrap(); + } + + #[test] + fn handle_zero() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let fib = python_fib(py); + let zero: BigInt = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap(); + assert_eq!(zero, BigInt::from(0)); + } + + /// `OverflowError` on converting Python int to BigInt, see issue #629 + #[test] + fn check_overflow() { + let gil = Python::acquire_gil(); + let py = gil.python(); + macro_rules! test { + ($T:ty, $value:expr, $py:expr) => { + let value = $value; + println!("{}: {}", stringify!($T), value); + let python_value = value.clone().to_object(py); + let roundtrip_value = python_value.extract::<$T>(py).unwrap(); + assert_eq!(value, roundtrip_value); + }; + } + for i in 0..=256usize { + // test a lot of values to help catch other bugs too + test!(BigInt, BigInt::from(i), py); + test!(BigUint, BigUint::from(i), py); + test!(BigInt, -BigInt::from(i), py); + test!(BigInt, BigInt::from(1) << i, py); + test!(BigUint, BigUint::from(1u32) << i, py); + test!(BigInt, -BigInt::from(1) << i, py); + test!(BigInt, (BigInt::from(1) << i) + 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) + 1u32, py); + test!(BigInt, (BigInt::from(1) << i) - 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) - 1u32, py); + } + } +} diff --git a/src/types/num.rs b/src/types/num.rs index 37201a49bd6..4cc42abd000 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -357,242 +357,6 @@ mod test_128bit_intergers { } } -#[cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] -#[cfg_attr( - docsrs, - doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))) -)] -mod bigint_conversion { - use super::*; - use crate::{err, Py}; - use num_bigint::{BigInt, BigUint}; - use std::os::raw::{c_int, c_uchar}; - - #[cfg(not(all(windows, PyPy)))] - unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> { - err::error_on_minusone( - ob.py(), - ffi::_PyLong_AsByteArray( - ob.as_ptr() as *mut ffi::PyLongObject, - buffer.as_mut_ptr(), - buffer.len(), - 1, - is_signed, - ), - ) - } - - macro_rules! bigint_conversion { - ($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => { - impl ToPyObject for $rust_ty { - fn to_object(&self, py: Python) -> PyObject { - unsafe { - let bytes = $to_bytes(self); - let obj = ffi::_PyLong_FromByteArray( - bytes.as_ptr() as *const c_uchar, - bytes.len(), - 1, - $is_signed, - ); - PyObject::from_owned_ptr(py, obj) - } - } - } - impl IntoPy for $rust_ty { - fn into_py(self, py: Python) -> PyObject { - self.to_object(py) - } - } - impl<'source> FromPyObject<'source> for $rust_ty { - fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> { - let py = ob.py(); - unsafe { - let num = ffi::PyNumber_Index(ob.as_ptr()); - if num.is_null() { - return Err(PyErr::fetch(py)); - } - let n_bits = ffi::_PyLong_NumBits(num); - let n_bytes = if n_bits < 0 { - return Err(PyErr::fetch(py)); - } else if n_bits == 0 { - 0 - } else { - (n_bits as usize - 1 + $is_signed) / 8 + 1 - }; - let num: Py = Py::from_owned_ptr(py, num); - if n_bytes <= 128 { - let mut buffer = [0; 128]; - extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?; - Ok($from_bytes(&buffer[..n_bytes])) - } else { - let mut buffer = vec![0; n_bytes]; - extract(num.as_ref(py), &mut buffer, $is_signed)?; - Ok($from_bytes(&buffer)) - } - } - } - } - }; - } - bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le); - bigint_conversion!( - BigInt, - 1, - BigInt::to_signed_bytes_le, - BigInt::from_signed_bytes_le - ); - - #[cfg(test)] - mod test { - use super::*; - use crate::types::{PyDict, PyModule}; - use indoc::indoc; - - fn python_fib(py: Python) -> &PyModule { - let fib_code = indoc!( - r#" - def fib(n): - f0, f1 = 0, 1 - for _ in range(n): - f0, f1 = f1, f0 + f1 - return f0 - - def fib_neg(n): - return -fib(n) - "# - ); - PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap() - } - - fn rust_fib(n: usize) -> T - where - T: From, - for<'a> &'a T: std::ops::Add, - { - let mut f0: T = T::from(0); - let mut f1: T = T::from(1); - for _ in 0..n { - let f2 = &f0 + &f1; - f0 = std::mem::replace(&mut f1, f2); - } - f0 - } - - #[test] - fn convert_biguint() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let rs_result: BigUint = rust_fib(400); - let fib = python_fib(py); - let locals = PyDict::new(py); - locals.set_item("rs_result", &rs_result).unwrap(); - locals.set_item("fib", fib).unwrap(); - // Checks if Rust BigUint -> Python Long conversion is correct - py.run("assert fib.fib(400) == rs_result", None, Some(locals)) - .unwrap(); - // Checks if Python Long -> Rust BigUint conversion is correct if N is small - let py_result: BigUint = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); - // Checks if Python Long -> Rust BigUint conversion is correct if N is large - let rs_result: BigUint = rust_fib(2000); - let py_result: BigUint = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); - } - - #[test] - fn convert_bigint() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let rs_result = rust_fib::(400) * -1; - let fib = python_fib(py); - let locals = PyDict::new(py); - locals.set_item("rs_result", &rs_result).unwrap(); - locals.set_item("fib", fib).unwrap(); - // Checks if Rust BigInt -> Python Long conversion is correct - py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals)) - .unwrap(); - // Checks if Python Long -> Rust BigInt conversion is correct if N is small - let py_result: BigInt = - FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap()) - .unwrap(); - assert_eq!(rs_result, py_result); - // Checks if Python Long -> Rust BigInt conversion is correct if N is large - let rs_result = rust_fib::(2000) * -1; - let py_result: BigInt = - FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap()) - .unwrap(); - assert_eq!(rs_result, py_result); - } - - fn python_index_class(py: Python) -> &PyModule { - let index_code = indoc!( - r#" - class C: - def __init__(self, x): - self.x = x - def __index__(self): - return self.x - "# - ); - PyModule::from_code(py, index_code, "index.py", "index").unwrap() - } - - #[test] - fn convert_index_class() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let index = python_index_class(py); - let locals = PyDict::new(py); - locals.set_item("index", index).unwrap(); - let ob = py.eval("index.C(10)", None, Some(locals)).unwrap(); - let _: BigInt = FromPyObject::extract(ob).unwrap(); - } - - #[test] - fn handle_zero() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let fib = python_fib(py); - let zero: BigInt = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap(); - assert_eq!(zero, BigInt::from(0)); - } - - /// `OverflowError` on converting Python int to BigInt, see issue #629 - #[test] - fn check_overflow() { - let gil = Python::acquire_gil(); - let py = gil.python(); - macro_rules! test { - ($T:ty, $value:expr, $py:expr) => { - let value = $value; - println!("{}: {}", stringify!($T), value); - let python_value = value.clone().to_object(py); - let roundtrip_value = python_value.extract::<$T>(py).unwrap(); - assert_eq!(value, roundtrip_value); - }; - } - for i in 0..=256usize { - // test a lot of values to help catch other bugs too - test!(BigInt, BigInt::from(i), py); - test!(BigUint, BigUint::from(i), py); - test!(BigInt, -BigInt::from(i), py); - test!(BigInt, BigInt::from(1) << i, py); - test!(BigUint, BigUint::from(1u32) << i, py); - test!(BigInt, -BigInt::from(1) << i, py); - test!(BigInt, (BigInt::from(1) << i) + 1u32, py); - test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py); - test!(BigInt, (-BigInt::from(1) << i) + 1u32, py); - test!(BigInt, (BigInt::from(1) << i) - 1u32, py); - test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py); - test!(BigInt, (-BigInt::from(1) << i) - 1u32, py); - } - } - } -} - #[cfg(test)] mod test { use crate::Python; From 430fdb4be2682ff4d1b38a43d487fb9be1d427e1 Mon Sep 17 00:00:00 2001 From: mejrs Date: Sat, 5 Jun 2021 15:48:57 +0200 Subject: [PATCH 2/8] add num_bigint docs --- src/lib.rs | 5 ----- src/num_bigint.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 26ccdd951d0..b785e62a1f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -298,11 +298,6 @@ mod python; pub mod type_object; pub mod types; -#[cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] -#[cfg_attr( - docsrs, - doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))) -)] pub mod num_bigint; #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] diff --git a/src/num_bigint.rs b/src/num_bigint.rs index e1a3bf23d36..d3be678cbfb 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -1,7 +1,56 @@ #![cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] - - - +#![cfg_attr( + docsrs, + doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))) +)] +//! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types. +//! +//! This is useful for converting Python integers, which have arbitrary precision, +//! when they may not fit in Rust's built-in integer types. +//! +//! # Setup +//! +//! To use this feature, add this to your **`Cargo.toml`**: +//! +//! ```toml +//! [dependencies] +//! num-bigint = "0.4" +//! pyo3 = { version = "0.14.0", features = ["num-bigint"] } +//! ``` +//! +//! Note that you must use compatible versions of num-bigint and PyO3. +//! The required num-bigint version may vary with older versions of PyO3. +//! +//! ## Examples +//! +//! [`BigInt`] and [`BigUint`] can be used represent arbitrary precision integers: +//! +//! ```rust +//! use num_bigint::BigInt; +//! use pyo3::prelude::*; +//! use pyo3::wrap_pyfunction; +//! +//! #[pyfunction] +//! fn add_one(n: BigInt) -> BigInt { +//! n + 1 +//! } +//! +//! #[pymodule] +//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> { +//! m.add_function(wrap_pyfunction!(add_one, m)?)?; +//! Ok(()) +//! } +//! ``` +//! +//! Python code: +//! ```python +//! from my_module import add_one +//! +//! n = 1 << 1337 +//! value = add_one(n) +//! +//! assert n + 1 == value +//! ``` use crate::{ From d5d2cf9fdaf629ba004ec5d359f6857537dd967d Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 7 Jun 2021 22:16:23 +0200 Subject: [PATCH 3/8] document + refactor numcomplex/bigint dependencies --- Cargo.toml | 2 + guide/src/features.md | 8 + src/lib.rs | 6 +- src/num_bigint.rs | 22 ++- src/num_complex.rs | 192 ++++++++++++++++++++ src/types/complex.rs | 400 ++++++++++++++++-------------------------- 6 files changed, 373 insertions(+), 257 deletions(-) create mode 100644 src/num_complex.rs diff --git a/Cargo.toml b/Cargo.toml index 132119c9e19..0b8df253d2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ proptest = { version = "0.10.1", default-features = false, features = ["std"] } # features needed to run the PyO3 test suite pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] } serde_json = "1.0.61" +# needed for num-complex doctest +nalgebra = "0.27.1" [build-dependencies] pyo3-build-config = { path = "pyo3-build-config", version = "=0.14.0-alpha.0" } diff --git a/guide/src/features.md b/guide/src/features.md index 9e21a40e708..bb7c3a42a1b 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -69,6 +69,14 @@ The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use R - `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol. - `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type. +### `num-bigint` + +This feature adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conversions into its [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUInt.html) types. + +### `num-complex` + +This feature adds a dependency on [num-complex](https://docs.rs/num-complex) and enables conversions into its [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type. + ### `serde` The `serde` feature enables (de)serialization of Py objects via [serde](https://serde.rs/). diff --git a/src/lib.rs b/src/lib.rs index 48f47c95ce1..21b5ea4ad29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,12 +80,12 @@ //! [`#[pyclass]`](crate::proc_macro::pyclass). This adds a dependency on the //! [`inventory`](https://docs.rs/inventory) crate, which is not supported on all platforms. // -//! - `num-bigint`: Enables conversions between Python objects and +//! - [`num-bigint`](crate::num_bigint): Enables conversions between Python objects and //! [num-bigint](https://docs.rs/num-bigint)'s //! [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and //! [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types. // -//! - `num-complex`: Enables conversions between Python objects and +//! - [`num-complex`](crate::num_complex): Enables conversions between Python objects and //! [num-complex](https://docs.rs/num-complex)'s //! [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type. // @@ -300,6 +300,8 @@ pub mod types; pub mod num_bigint; +pub mod num_complex; + #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg(feature = "serde")] pub mod serde; diff --git a/src/num_bigint.rs b/src/num_bigint.rs index d3be678cbfb..be98584884e 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -1,3 +1,7 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + #![cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] #![cfg_attr( docsrs, @@ -5,8 +9,7 @@ )] //! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types. //! -//! This is useful for converting Python integers, which have arbitrary precision, -//! when they may not fit in Rust's built-in integer types. +//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types. //! //! # Setup //! @@ -15,7 +18,7 @@ //! ```toml //! [dependencies] //! num-bigint = "0.4" -//! pyo3 = { version = "0.14.0", features = ["num-bigint"] } +//! pyo3 = { version = "0.14", features = ["num-bigint"] } //! ``` //! //! Note that you must use compatible versions of num-bigint and PyO3. @@ -23,18 +26,20 @@ //! //! ## Examples //! -//! [`BigInt`] and [`BigUint`] can be used represent arbitrary precision integers: +//! Using [`BigInt`] to correctly increment an arbitrary precision integer. +//! This is not possible with Rust's native integers if the Python integer is too large, +//! in which case it will fail its conversion and raise `OverflowError`. //! //! ```rust //! use num_bigint::BigInt; //! use pyo3::prelude::*; //! use pyo3::wrap_pyfunction; -//! +//! //! #[pyfunction] //! fn add_one(n: BigInt) -> BigInt { //! n + 1 //! } -//! +//! //! #[pymodule] //! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(add_one, m)?)?; @@ -45,14 +50,13 @@ //! Python code: //! ```python //! from my_module import add_one -//! +//! //! n = 1 << 1337 //! value = add_one(n) -//! +//! //! assert n + 1 == value //! ``` - use crate::{ err, ffi, types::*, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, ToPyObject, diff --git a/src/num_complex.rs b/src/num_complex.rs new file mode 100644 index 00000000000..801e7aca548 --- /dev/null +++ b/src/num_complex.rs @@ -0,0 +1,192 @@ +#![cfg(feature = "num-complex")] +#![cfg_attr(docsrs, doc(cfg(feature = "num-complex")))] +//! Conversions to and from [num-complex](https://docs.rs/num-complex)’ +//! [`Complex`]`<`[`f32`]`>` and [`Complex`]`<`[`f64`]`>`. +//! +//! num-complex’ [`Complex`] supports more operations than PyO3's [`PyComplex`] +//! and can be used with the rest of the Rust ecosystem. +//! +//! # Setup +//! +//! To use this feature, add this to your **`Cargo.toml`**: +//! +//! ```toml +//! [dependencies] +//! num-complex = "0.4" +//! pyo3 = { version = "0.14", features = ["num-complex"] } +//! ``` +//! +//! Note that you must use compatible versions of num-complex and PyO3. +//! The required num-complex version may vary with older versions of PyO3. +//! +//! # Examples +//! +//! Using [num-complex](https://docs.rs/num-complex) and [nalgebra](https://docs.rs/nalgebra) +//! to create a pyfunction that calculates the eigenvalues of a 2x2 matrix. +//! +//! ```rust +//! use nalgebra::base::{dimension::Const, storage::Storage, Matrix}; +//! use num_complex::Complex; +//! use pyo3::prelude::*; +//! use pyo3::wrap_pyfunction; +//! +//! type T = Complex; +//! +//! #[pyfunction] +//! fn get_eigenvalues(m11: T, m12: T, m21: T, m22: T) -> Vec { +//! let mat = Matrix::, Const<2>, _>::new(m11, m12, m21, m22); +//! +//! match mat.eigenvalues() { +//! Some(e) => e.data.as_slice().to_vec(), +//! None => vec![], +//! } +//! } +//! +//! #[pymodule] +//! fn my_module(_py: Python, m: &PyModule) -> PyResult<()> { +//! m.add_function(wrap_pyfunction!(get_eigenvalues, m)?)?; +//! Ok(()) +//! } +//! # // test +//! # use assert_approx_eq::assert_approx_eq; +//! # use nalgebra::ComplexField; +//! # use pyo3::types::PyComplex; +//! # +//! # fn main() -> PyResult<()> { +//! # Python::with_gil(|py| -> PyResult<()> { +//! # let module = PyModule::new(py, "my_module")?; +//! # +//! # module.add_function(wrap_pyfunction!(get_eigenvalues, module)?)?; +//! # +//! # let m11 = PyComplex::from_doubles(py, 0_f64, -1_f64); +//! # let m12 = PyComplex::from_doubles(py, 1_f64, 0_f64); +//! # let m21 = PyComplex::from_doubles(py, 2_f64, -1_f64); +//! # let m22 = PyComplex::from_doubles(py, -1_f64, 0_f64); +//! # +//! # let result = module +//! # .getattr("get_eigenvalues")? +//! # .call1((m11, m12, m21, m22))?; +//! # println!("eigenvalues: {:?}", result); +//! # +//! # let result = result.extract::>()?; +//! # let e0 = result[0]; +//! # let e1 = result[1]; +//! # +//! # assert_approx_eq!(e0, Complex::new(1_f64, -1_f64)); +//! # assert_approx_eq!(e1, Complex::new(-2_f64, 0_f64)); +//! # +//! # Ok(()) +//! # }) +//! # } +//! ``` +//! +//! Python code: +//! ```python +//! from my_module import get_eigenvalues +//! +//! m11 = complex(0,-1) +//! m12 = complex(1,0) +//! m21 = complex(2,-1) +//! m22 = complex(-1,0) +//! +//! result = get_eigenvalues(m11,m12,m21,m22) +//! assert result == [complex(1,-1), complex(-2,0)] +//! ``` +use crate::{ + ffi, types::PyComplex, AsPyPointer, FromPyObject, PyAny, PyErr, PyNativeType, PyObject, + PyResult, Python, ToPyObject, +}; +use num_complex::Complex; +use std::os::raw::c_double; + +impl PyComplex { + /// Creates a new Python `PyComplex` object from `num_complex`'s [`Complex`]. + pub fn from_complex>(py: Python, complex: Complex) -> &PyComplex { + unsafe { + let ptr = ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into()); + py.from_owned_ptr(ptr) + } + } +} + +macro_rules! complex_conversion { + ($float: ty) => { + impl ToPyObject for Complex<$float> { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + crate::IntoPy::::into_py(self.to_owned(), py) + } + } + impl crate::IntoPy for Complex<$float> { + fn into_py(self, py: Python) -> PyObject { + unsafe { + let raw_obj = + ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double); + PyObject::from_owned_ptr(py, raw_obj) + } + } + } + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[allow(clippy::float_cmp)] // The comparison is for an error value + impl<'source> FromPyObject<'source> for Complex<$float> { + fn extract(obj: &'source PyAny) -> PyResult> { + unsafe { + let val = ffi::PyComplex_AsCComplex(obj.as_ptr()); + if val.real == -1.0 && PyErr::occurred(obj.py()) { + Err(PyErr::fetch(obj.py())) + } else { + Ok(Complex::new(val.real as $float, val.imag as $float)) + } + } + } + } + #[cfg(any(Py_LIMITED_API, PyPy))] + #[allow(clippy::float_cmp)] // The comparison is for an error value + impl<'source> FromPyObject<'source> for Complex<$float> { + fn extract(obj: &'source PyAny) -> PyResult> { + unsafe { + let ptr = obj.as_ptr(); + let real = ffi::PyComplex_RealAsDouble(ptr); + if real == -1.0 && PyErr::occurred(obj.py()) { + return Err(PyErr::fetch(obj.py())); + } + let imag = ffi::PyComplex_ImagAsDouble(ptr); + Ok(Complex::new(real as $float, imag as $float)) + } + } + } + }; +} +complex_conversion!(f32); +complex_conversion!(f64); + +#[cfg(test)] +mod test { + use super::*; + + #[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip + #[test] + fn from_complex() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let complex = Complex::new(3.0, 1.2); + let py_c = PyComplex::from_complex(py, complex); + assert_eq!(py_c.real(), 3.0); + assert_eq!(py_c.imag(), 1.2); + } + #[test] + fn to_from_complex() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = Complex::new(3.0, 1.2); + let obj = val.to_object(py); + assert_eq!(obj.extract::>(py).unwrap(), val); + } + #[test] + fn from_complex_err() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = vec![1].to_object(py); + assert!(obj.extract::>(py).is_err()); + } +} diff --git a/src/types/complex.rs b/src/types/complex.rs index 70b782e4761..cb8f7bbb93c 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -1,11 +1,13 @@ -#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] -use crate::instance::PyNativeType; use crate::{ffi, AsPyPointer, PyAny, Python}; -#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] -use std::ops::*; use std::os::raw::c_double; -/// Represents a Python `complex`. +/// Represents a Python [`complex`](https://docs.python.org/3/library/functions.html#complex) object. +/// +/// Note that `PyComplex` supports only basic operations. For advanced operations +/// consider using [num-bigint](https://docs.rs/num-bigint)'s [`Complex`] type instead. +/// This requires the [`num-complex`](crate::num_complex) feature flag. +/// +/// [`Complex`]: https://docs.rs/num-complex/latest/num_complex/struct.Complex.html #[repr(transparent)] pub struct PyComplex(PyAny); @@ -17,7 +19,7 @@ pyobject_native_type!( ); impl PyComplex { - /// Creates a new Python `complex` object, from its real and imaginary values. + /// Creates a new `PyComplex` from the given real and imaginary values. pub fn from_doubles(py: Python, real: c_double, imag: c_double) -> &PyComplex { unsafe { let ptr = ffi::PyComplex_FromDoubles(real, imag); @@ -25,197 +27,183 @@ impl PyComplex { } } /// Returns the real part of the complex number. + #[must_use = "method returns a new number and does not mutate the original value"] pub fn real(&self) -> c_double { unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) } } - /// Returns the imaginary part the complex number. + /// Returns the imaginary part of the complex number. + #[must_use = "method returns a new number and does not mutate the original value"] pub fn imag(&self) -> c_double { unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) } } - /// Returns `|self|`. - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] - pub fn abs(&self) -> c_double { - unsafe { - let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; - ffi::_Py_c_abs(val) - } - } - /// Returns `self ** other` - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] - pub fn pow(&self, other: &PyComplex) -> &PyComplex { - unsafe { - self.py() - .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_pow)) - } - } -} - -#[cfg(not(any(Py_LIMITED_API, PyPy)))] -#[inline(always)] -unsafe fn complex_operation( - l: &PyComplex, - r: &PyComplex, - operation: unsafe extern "C" fn(ffi::Py_complex, ffi::Py_complex) -> ffi::Py_complex, -) -> *mut ffi::PyObject { - let l_val = (*(l.as_ptr() as *mut ffi::PyComplexObject)).cval; - let r_val = (*(r.as_ptr() as *mut ffi::PyComplexObject)).cval; - ffi::PyComplex_FromCComplex(operation(l_val, r_val)) } #[cfg(not(any(Py_LIMITED_API, PyPy)))] #[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] -impl<'py> Add for &'py PyComplex { - type Output = &'py PyComplex; - fn add(self, other: &'py PyComplex) -> &'py PyComplex { - unsafe { - self.py() - .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_sum)) +mod not_limited_impls { + use super::*; + use crate::instance::PyNativeType; + use std::ops::{Add, Div, Mul, Neg, Sub}; + + impl PyComplex { + /// Returns `|self|`. + #[must_use = "method returns a new number and does not mutate the original value"] + pub fn abs(&self) -> c_double { + unsafe { + let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; + ffi::_Py_c_abs(val) + } + } + /// Returns `self` raised to the power of `other`. + #[must_use = "method returns a new PyComplex and does not mutate the original value"] + pub fn pow(&self, other: &PyComplex) -> &PyComplex { + unsafe { + self.py() + .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_pow)) + } } } -} -#[cfg(not(any(Py_LIMITED_API, PyPy)))] -#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] -impl<'py> Sub for &'py PyComplex { - type Output = &'py PyComplex; - fn sub(self, other: &'py PyComplex) -> &'py PyComplex { - unsafe { - self.py() - .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_diff)) - } + #[inline(always)] + unsafe fn complex_operation( + l: &PyComplex, + r: &PyComplex, + operation: unsafe extern "C" fn(ffi::Py_complex, ffi::Py_complex) -> ffi::Py_complex, + ) -> *mut ffi::PyObject { + let l_val = (*(l.as_ptr() as *mut ffi::PyComplexObject)).cval; + let r_val = (*(r.as_ptr() as *mut ffi::PyComplexObject)).cval; + ffi::PyComplex_FromCComplex(operation(l_val, r_val)) } -} -#[cfg(not(any(Py_LIMITED_API, PyPy)))] -#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] -impl<'py> Mul for &'py PyComplex { - type Output = &'py PyComplex; - fn mul(self, other: &'py PyComplex) -> &'py PyComplex { - unsafe { - self.py() - .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_prod)) + impl<'py> Add for &'py PyComplex { + type Output = &'py PyComplex; + fn add(self, other: &'py PyComplex) -> &'py PyComplex { + unsafe { + self.py() + .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_sum)) + } } } -} -#[cfg(not(any(Py_LIMITED_API, PyPy)))] -#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] -impl<'py> Div for &'py PyComplex { - type Output = &'py PyComplex; - fn div(self, other: &'py PyComplex) -> &'py PyComplex { - unsafe { - self.py() - .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_quot)) + impl<'py> Sub for &'py PyComplex { + type Output = &'py PyComplex; + fn sub(self, other: &'py PyComplex) -> &'py PyComplex { + unsafe { + self.py() + .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_diff)) + } } } -} -#[cfg(not(any(Py_LIMITED_API, PyPy)))] -#[cfg_attr(docsrs, doc(cfg(not(any(Py_LIMITED_API, PyPy)))))] -impl<'py> Neg for &'py PyComplex { - type Output = &'py PyComplex; - fn neg(self) -> &'py PyComplex { - unsafe { - let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; - self.py() - .from_owned_ptr(ffi::PyComplex_FromCComplex(ffi::_Py_c_neg(val))) + impl<'py> Mul for &'py PyComplex { + type Output = &'py PyComplex; + fn mul(self, other: &'py PyComplex) -> &'py PyComplex { + unsafe { + self.py() + .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_prod)) + } } } -} -#[cfg(feature = "num-complex")] -#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))] -mod complex_conversion { - use super::*; - use crate::{FromPyObject, PyErr, PyNativeType, PyObject, PyResult, ToPyObject}; - use num_complex::Complex; - - impl PyComplex { - /// Creates a new Python `PyComplex` object from `num_complex`'s [`Complex`]. - pub fn from_complex>(py: Python, complex: Complex) -> &PyComplex { + impl<'py> Div for &'py PyComplex { + type Output = &'py PyComplex; + fn div(self, other: &'py PyComplex) -> &'py PyComplex { unsafe { - let ptr = ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into()); - py.from_owned_ptr(ptr) + self.py() + .from_owned_ptr(complex_operation(self, other, ffi::_Py_c_quot)) } } } - macro_rules! complex_conversion { - ($float: ty) => { - impl ToPyObject for Complex<$float> { - #[inline] - fn to_object(&self, py: Python) -> PyObject { - crate::IntoPy::::into_py(self.to_owned(), py) - } - } - impl crate::IntoPy for Complex<$float> { - fn into_py(self, py: Python) -> PyObject { - unsafe { - let raw_obj = - ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double); - PyObject::from_owned_ptr(py, raw_obj) - } - } - } - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[allow(clippy::float_cmp)] // The comparison is for an error value - impl<'source> FromPyObject<'source> for Complex<$float> { - fn extract(obj: &'source PyAny) -> PyResult> { - unsafe { - let val = ffi::PyComplex_AsCComplex(obj.as_ptr()); - if val.real == -1.0 && PyErr::occurred(obj.py()) { - Err(PyErr::fetch(obj.py())) - } else { - Ok(Complex::new(val.real as $float, val.imag as $float)) - } - } - } - } - #[cfg(any(Py_LIMITED_API, PyPy))] - #[allow(clippy::float_cmp)] // The comparison is for an error value - impl<'source> FromPyObject<'source> for Complex<$float> { - fn extract(obj: &'source PyAny) -> PyResult> { - unsafe { - let ptr = obj.as_ptr(); - let real = ffi::PyComplex_RealAsDouble(ptr); - if real == -1.0 && PyErr::occurred(obj.py()) { - return Err(PyErr::fetch(obj.py())); - } - let imag = ffi::PyComplex_ImagAsDouble(ptr); - Ok(Complex::new(real as $float, imag as $float)) - } - } + + impl<'py> Neg for &'py PyComplex { + type Output = &'py PyComplex; + fn neg(self) -> &'py PyComplex { + unsafe { + let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; + self.py() + .from_owned_ptr(ffi::PyComplex_FromCComplex(ffi::_Py_c_neg(val))) } - }; + } } - complex_conversion!(f32); - complex_conversion!(f64); - #[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip - #[test] - fn from_complex() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let complex = Complex::new(3.0, 1.2); - let py_c = PyComplex::from_complex(py, complex); - assert_eq!(py_c.real(), 3.0); - assert_eq!(py_c.imag(), 1.2); - } - #[test] - fn to_from_complex() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = Complex::new(3.0, 1.2); - let obj = val.to_object(py); - assert_eq!(obj.extract::>(py).unwrap(), val); - } - #[test] - fn from_complex_err() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = vec![1].to_object(py); - assert!(obj.extract::>(py).is_err()); + #[cfg(test)] + mod test { + use super::PyComplex; + use crate::Python; + use assert_approx_eq::assert_approx_eq; + + #[test] + fn test_add() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l + r; + assert_approx_eq!(res.real(), 4.0); + assert_approx_eq!(res.imag(), 3.8); + } + + #[test] + fn test_sub() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l - r; + assert_approx_eq!(res.real(), 2.0); + assert_approx_eq!(res.imag(), -1.4); + } + + #[test] + fn test_mul() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l * r; + assert_approx_eq!(res.real(), -0.12); + assert_approx_eq!(res.imag(), 9.0); + } + + #[test] + fn test_div() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l / r; + assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); + assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); + } + + #[test] + fn test_neg() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = PyComplex::from_doubles(py, 3.0, 1.2); + let res = -val; + assert_approx_eq!(res.real(), -3.0); + assert_approx_eq!(res.imag(), -1.2); + } + + #[test] + fn test_abs() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = PyComplex::from_doubles(py, 3.0, 1.2); + assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); + } + + #[test] + fn test_pow() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.2, 2.6); + let val = l.pow(r); + assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); + assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); + } } } @@ -235,84 +223,4 @@ mod test { assert_approx_eq!(complex.real(), 3.0); assert_approx_eq!(complex.imag(), 1.2); } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_add() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l + r; - assert_approx_eq!(res.real(), 4.0); - assert_approx_eq!(res.imag(), 3.8); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_sub() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l - r; - assert_approx_eq!(res.real(), 2.0); - assert_approx_eq!(res.imag(), -1.4); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_mul() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l * r; - assert_approx_eq!(res.real(), -0.12); - assert_approx_eq!(res.imag(), 9.0); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_div() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l / r; - assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); - assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_neg() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = PyComplex::from_doubles(py, 3.0, 1.2); - let res = -val; - assert_approx_eq!(res.real(), -3.0); - assert_approx_eq!(res.imag(), -1.2); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_abs() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = PyComplex::from_doubles(py, 3.0, 1.2); - assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); - } - - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - #[test] - fn test_pow() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.2, 2.6); - let val = l.pow(r); - assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); - assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); - } } From 4677eddf5a999643c9c817cb6bffac246686002b Mon Sep 17 00:00:00 2001 From: mejrs Date: Tue, 8 Jun 2021 13:11:04 +0200 Subject: [PATCH 4/8] fixed dead module link on pypy --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 21b5ea4ad29..fa7b0f397b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ //! [`#[pyclass]`](crate::proc_macro::pyclass). This adds a dependency on the //! [`inventory`](https://docs.rs/inventory) crate, which is not supported on all platforms. // -//! - [`num-bigint`](crate::num_bigint): Enables conversions between Python objects and +//! - [`num-bigint`](./num_bigint/index.html): Enables conversions between Python objects and //! [num-bigint](https://docs.rs/num-bigint)'s //! [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and //! [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types. From 4a0ee6bf0f125136b81cf49723cbbfc19ef1c216 Mon Sep 17 00:00:00 2001 From: mejrs Date: Tue, 8 Jun 2021 13:13:44 +0200 Subject: [PATCH 5/8] removed nalgebra test dep, add updating pyo3 vers --- Cargo.toml | 2 -- src/num_bigint.rs | 6 +++--- src/num_complex.rs | 10 ++++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b8df253d2c..132119c9e19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,6 @@ proptest = { version = "0.10.1", default-features = false, features = ["std"] } # features needed to run the PyO3 test suite pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] } serde_json = "1.0.61" -# needed for num-complex doctest -nalgebra = "0.27.1" [build-dependencies] pyo3-build-config = { path = "pyo3-build-config", version = "=0.14.0-alpha.0" } diff --git a/src/num_bigint.rs b/src/num_bigint.rs index be98584884e..f63746779f6 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -17,12 +17,12 @@ //! //! ```toml //! [dependencies] -//! num-bigint = "0.4" -//! pyo3 = { version = "0.14", features = ["num-bigint"] } +//! num-bigint = "*" +#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION", "unable to get current pyo3 version"), "\", features = [\"num-bigint\"] }")] //! ``` //! //! Note that you must use compatible versions of num-bigint and PyO3. -//! The required num-bigint version may vary with older versions of PyO3. +//! The required num-bigint version may vary based on the version of PyO3. //! //! ## Examples //! diff --git a/src/num_complex.rs b/src/num_complex.rs index 801e7aca548..c237b88a46d 100644 --- a/src/num_complex.rs +++ b/src/num_complex.rs @@ -12,19 +12,21 @@ //! //! ```toml //! [dependencies] -//! num-complex = "0.4" -//! pyo3 = { version = "0.14", features = ["num-complex"] } +//! num-complex = "*" +#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION", "unable to get current pyo3 version"), "\", features = [\"num-complex\"] }")] //! ``` //! //! Note that you must use compatible versions of num-complex and PyO3. -//! The required num-complex version may vary with older versions of PyO3. +//! The required num-complex version may vary based on the version of PyO3. //! //! # Examples //! //! Using [num-complex](https://docs.rs/num-complex) and [nalgebra](https://docs.rs/nalgebra) //! to create a pyfunction that calculates the eigenvalues of a 2x2 matrix. //! -//! ```rust +//! ```ignore +//! # // not tested because nalgebra isn't supported on msrv +//! # // please file an issue if it breaks! //! use nalgebra::base::{dimension::Const, storage::Storage, Matrix}; //! use num_complex::Complex; //! use pyo3::prelude::*; From 14eab0e0da75df76823fb2c7f7366aac8b586252 Mon Sep 17 00:00:00 2001 From: mejrs Date: Tue, 8 Jun 2021 13:14:02 +0200 Subject: [PATCH 6/8] remove must_use on pycomplex methods --- src/types/complex.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/types/complex.rs b/src/types/complex.rs index cb8f7bbb93c..d27b43fcf5c 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -27,12 +27,10 @@ impl PyComplex { } } /// Returns the real part of the complex number. - #[must_use = "method returns a new number and does not mutate the original value"] pub fn real(&self) -> c_double { unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) } } /// Returns the imaginary part of the complex number. - #[must_use = "method returns a new number and does not mutate the original value"] pub fn imag(&self) -> c_double { unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) } } @@ -47,7 +45,6 @@ mod not_limited_impls { impl PyComplex { /// Returns `|self|`. - #[must_use = "method returns a new number and does not mutate the original value"] pub fn abs(&self) -> c_double { unsafe { let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; @@ -55,7 +52,6 @@ mod not_limited_impls { } } /// Returns `self` raised to the power of `other`. - #[must_use = "method returns a new PyComplex and does not mutate the original value"] pub fn pow(&self, other: &PyComplex) -> &PyComplex { unsafe { self.py() From e2c698ad172f81a17aab02892399508e8cc670ba Mon Sep 17 00:00:00 2001 From: mejrs Date: Wed, 9 Jun 2021 15:39:33 +0200 Subject: [PATCH 7/8] put * for version in cargo.toml examples --- src/num_bigint.rs | 3 ++- src/num_complex.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/num_bigint.rs b/src/num_bigint.rs index f63746779f6..b9d30474713 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -17,8 +17,9 @@ //! //! ```toml //! [dependencies] +//! # change * to the latest versions //! num-bigint = "*" -#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION", "unable to get current pyo3 version"), "\", features = [\"num-bigint\"] }")] +//! pyo3 = { version = "*", features = ["num-bigint"] } //! ``` //! //! Note that you must use compatible versions of num-bigint and PyO3. diff --git a/src/num_complex.rs b/src/num_complex.rs index c237b88a46d..73b85e98e3e 100644 --- a/src/num_complex.rs +++ b/src/num_complex.rs @@ -12,8 +12,9 @@ //! //! ```toml //! [dependencies] +//! # change * to the latest versions //! num-complex = "*" -#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION", "unable to get current pyo3 version"), "\", features = [\"num-complex\"] }")] +//! pyo3 = { version = "*", features = ["num-complex"] } //! ``` //! //! Note that you must use compatible versions of num-complex and PyO3. From f50521a083f28223ee18cb5758b65f20a0417b00 Mon Sep 17 00:00:00 2001 From: mejrs Date: Thu, 10 Jun 2021 14:24:11 +0200 Subject: [PATCH 8/8] use Python::with_gil in tests --- src/num_bigint.rs | 157 ++++++++++++++++++++++--------------------- src/num_complex.rs | 30 ++++----- src/types/complex.rs | 100 +++++++++++++-------------- 3 files changed, 145 insertions(+), 142 deletions(-) diff --git a/src/num_bigint.rs b/src/num_bigint.rs index b9d30474713..aa2050257f4 100644 --- a/src/num_bigint.rs +++ b/src/num_bigint.rs @@ -178,48 +178,50 @@ mod test { #[test] fn convert_biguint() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let rs_result: BigUint = rust_fib(400); - let fib = python_fib(py); - let locals = PyDict::new(py); - locals.set_item("rs_result", &rs_result).unwrap(); - locals.set_item("fib", fib).unwrap(); - // Checks if Rust BigUint -> Python Long conversion is correct - py.run("assert fib.fib(400) == rs_result", None, Some(locals)) - .unwrap(); - // Checks if Python Long -> Rust BigUint conversion is correct if N is small - let py_result: BigUint = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); - // Checks if Python Long -> Rust BigUint conversion is correct if N is large - let rs_result: BigUint = rust_fib(2000); - let py_result: BigUint = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); + Python::with_gil(|py| { + let rs_result: BigUint = rust_fib(400); + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigUint -> Python Long conversion is correct + py.run("assert fib.fib(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigUint conversion is correct if N is small + let py_result: BigUint = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((400,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigUint conversion is correct if N is large + let rs_result: BigUint = rust_fib(2000); + let py_result: BigUint = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((2000,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + }); } #[test] fn convert_bigint() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let rs_result = rust_fib::(400) * -1; - let fib = python_fib(py); - let locals = PyDict::new(py); - locals.set_item("rs_result", &rs_result).unwrap(); - locals.set_item("fib", fib).unwrap(); - // Checks if Rust BigInt -> Python Long conversion is correct - py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals)) - .unwrap(); - // Checks if Python Long -> Rust BigInt conversion is correct if N is small - let py_result: BigInt = - FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); - // Checks if Python Long -> Rust BigInt conversion is correct if N is large - let rs_result = rust_fib::(2000) * -1; - let py_result: BigInt = - FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap()).unwrap(); - assert_eq!(rs_result, py_result); + Python::with_gil(|py| { + let rs_result = rust_fib::(400) * -1; + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigInt -> Python Long conversion is correct + py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigInt conversion is correct if N is small + let py_result: BigInt = + FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((400,)).unwrap()) + .unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigInt conversion is correct if N is large + let rs_result = rust_fib::(2000) * -1; + let py_result: BigInt = + FromPyObject::extract(fib.getattr("fib_neg").unwrap().call1((2000,)).unwrap()) + .unwrap(); + assert_eq!(rs_result, py_result); + }) } fn python_index_class(py: Python) -> &PyModule { @@ -237,53 +239,54 @@ mod test { #[test] fn convert_index_class() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let index = python_index_class(py); - let locals = PyDict::new(py); - locals.set_item("index", index).unwrap(); - let ob = py.eval("index.C(10)", None, Some(locals)).unwrap(); - let _: BigInt = FromPyObject::extract(ob).unwrap(); + Python::with_gil(|py| { + let index = python_index_class(py); + let locals = PyDict::new(py); + locals.set_item("index", index).unwrap(); + let ob = py.eval("index.C(10)", None, Some(locals)).unwrap(); + let _: BigInt = FromPyObject::extract(ob).unwrap(); + }); } #[test] fn handle_zero() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let fib = python_fib(py); - let zero: BigInt = - FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap(); - assert_eq!(zero, BigInt::from(0)); + Python::with_gil(|py| { + let fib = python_fib(py); + let zero: BigInt = + FromPyObject::extract(fib.getattr("fib").unwrap().call1((0,)).unwrap()).unwrap(); + assert_eq!(zero, BigInt::from(0)); + }) } /// `OverflowError` on converting Python int to BigInt, see issue #629 #[test] fn check_overflow() { - let gil = Python::acquire_gil(); - let py = gil.python(); - macro_rules! test { - ($T:ty, $value:expr, $py:expr) => { - let value = $value; - println!("{}: {}", stringify!($T), value); - let python_value = value.clone().to_object(py); - let roundtrip_value = python_value.extract::<$T>(py).unwrap(); - assert_eq!(value, roundtrip_value); - }; - } - for i in 0..=256usize { - // test a lot of values to help catch other bugs too - test!(BigInt, BigInt::from(i), py); - test!(BigUint, BigUint::from(i), py); - test!(BigInt, -BigInt::from(i), py); - test!(BigInt, BigInt::from(1) << i, py); - test!(BigUint, BigUint::from(1u32) << i, py); - test!(BigInt, -BigInt::from(1) << i, py); - test!(BigInt, (BigInt::from(1) << i) + 1u32, py); - test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py); - test!(BigInt, (-BigInt::from(1) << i) + 1u32, py); - test!(BigInt, (BigInt::from(1) << i) - 1u32, py); - test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py); - test!(BigInt, (-BigInt::from(1) << i) - 1u32, py); - } + Python::with_gil(|py| { + macro_rules! test { + ($T:ty, $value:expr, $py:expr) => { + let value = $value; + println!("{}: {}", stringify!($T), value); + let python_value = value.clone().to_object(py); + let roundtrip_value = python_value.extract::<$T>(py).unwrap(); + assert_eq!(value, roundtrip_value); + }; + } + + for i in 0..=256usize { + // test a lot of values to help catch other bugs too + test!(BigInt, BigInt::from(i), py); + test!(BigUint, BigUint::from(i), py); + test!(BigInt, -BigInt::from(i), py); + test!(BigInt, BigInt::from(1) << i, py); + test!(BigUint, BigUint::from(1u32) << i, py); + test!(BigInt, -BigInt::from(1) << i, py); + test!(BigInt, (BigInt::from(1) << i) + 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) + 1u32, py); + test!(BigInt, (BigInt::from(1) << i) - 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) - 1u32, py); + } + }); } } diff --git a/src/num_complex.rs b/src/num_complex.rs index 73b85e98e3e..4e4776a0df8 100644 --- a/src/num_complex.rs +++ b/src/num_complex.rs @@ -170,26 +170,26 @@ mod test { #[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip #[test] fn from_complex() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let complex = Complex::new(3.0, 1.2); - let py_c = PyComplex::from_complex(py, complex); - assert_eq!(py_c.real(), 3.0); - assert_eq!(py_c.imag(), 1.2); + Python::with_gil(|py| { + let complex = Complex::new(3.0, 1.2); + let py_c = PyComplex::from_complex(py, complex); + assert_eq!(py_c.real(), 3.0); + assert_eq!(py_c.imag(), 1.2); + }); } #[test] fn to_from_complex() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = Complex::new(3.0, 1.2); - let obj = val.to_object(py); - assert_eq!(obj.extract::>(py).unwrap(), val); + Python::with_gil(|py| { + let val = Complex::new(3.0, 1.2); + let obj = val.to_object(py); + assert_eq!(obj.extract::>(py).unwrap(), val); + }); } #[test] fn from_complex_err() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = vec![1].to_object(py); - assert!(obj.extract::>(py).is_err()); + Python::with_gil(|py| { + let obj = vec![1].to_object(py); + assert!(obj.extract::>(py).is_err()); + }); } } diff --git a/src/types/complex.rs b/src/types/complex.rs index d27b43fcf5c..a4de4e0f8ae 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -130,75 +130,75 @@ mod not_limited_impls { #[test] fn test_add() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l + r; - assert_approx_eq!(res.real(), 4.0); - assert_approx_eq!(res.imag(), 3.8); + Python::with_gil(|py| { + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l + r; + assert_approx_eq!(res.real(), 4.0); + assert_approx_eq!(res.imag(), 3.8); + }); } #[test] fn test_sub() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l - r; - assert_approx_eq!(res.real(), 2.0); - assert_approx_eq!(res.imag(), -1.4); + Python::with_gil(|py| { + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l - r; + assert_approx_eq!(res.real(), 2.0); + assert_approx_eq!(res.imag(), -1.4); + }); } #[test] fn test_mul() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l * r; - assert_approx_eq!(res.real(), -0.12); - assert_approx_eq!(res.imag(), 9.0); + Python::with_gil(|py| { + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l * r; + assert_approx_eq!(res.real(), -0.12); + assert_approx_eq!(res.imag(), 9.0); + }); } #[test] fn test_div() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.0, 2.6); - let res = l / r; - assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); - assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); + Python::with_gil(|py| { + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l / r; + assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); + assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); + }); } #[test] fn test_neg() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = PyComplex::from_doubles(py, 3.0, 1.2); - let res = -val; - assert_approx_eq!(res.real(), -3.0); - assert_approx_eq!(res.imag(), -1.2); + Python::with_gil(|py| { + let val = PyComplex::from_doubles(py, 3.0, 1.2); + let res = -val; + assert_approx_eq!(res.real(), -3.0); + assert_approx_eq!(res.imag(), -1.2); + }); } #[test] fn test_abs() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let val = PyComplex::from_doubles(py, 3.0, 1.2); - assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); + Python::with_gil(|py| { + let val = PyComplex::from_doubles(py, 3.0, 1.2); + assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); + }); } #[test] fn test_pow() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let l = PyComplex::from_doubles(py, 3.0, 1.2); - let r = PyComplex::from_doubles(py, 1.2, 2.6); - let val = l.pow(r); - assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); - assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); + Python::with_gil(|py| { + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.2, 2.6); + let val = l.pow(r); + assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); + assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); + }); } } } @@ -213,10 +213,10 @@ mod test { fn test_from_double() { use assert_approx_eq::assert_approx_eq; - let gil = Python::acquire_gil(); - let py = gil.python(); - let complex = PyComplex::from_doubles(py, 3.0, 1.2); - assert_approx_eq!(complex.real(), 3.0); - assert_approx_eq!(complex.imag(), 1.2); + Python::with_gil(|py| { + let complex = PyComplex::from_doubles(py, 3.0, 1.2); + assert_approx_eq!(complex.real(), 3.0); + assert_approx_eq!(complex.imag(), 1.2); + }); } }