1 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
2 | use crate::py_result_ext::PyResultExt; |
3 | use crate::{ffi, types::any::PyAnyMethods, Bound, PyAny, Python}; |
4 | use std::os::raw::c_double; |
5 | |
6 | /// Represents a Python [`complex`](https://docs.python.org/3/library/functions.html#complex) object. |
7 | /// |
8 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
9 | /// [`Py<PyComplex>`][crate::Py] or [`Bound<'py, PyComplex>`][Bound]. |
10 | /// |
11 | /// For APIs available on `complex` objects, see the [`PyComplexMethods`] trait which is implemented for |
12 | /// [`Bound<'py, PyComplex>`][Bound]. |
13 | /// |
14 | /// Note that `PyComplex` supports only basic operations. For advanced operations |
15 | /// consider using [num-complex](https://docs.rs/num-complex)'s [`Complex`] type instead. |
16 | /// This optional dependency can be activated with the `num-complex` feature flag. |
17 | /// |
18 | /// [`Complex`]: https://docs.rs/num-complex/latest/num_complex/struct.Complex.html |
19 | #[repr (transparent)] |
20 | pub struct PyComplex(PyAny); |
21 | |
22 | pyobject_subclassable_native_type!(PyComplex, ffi::PyComplexObject); |
23 | |
24 | pyobject_native_type!( |
25 | PyComplex, |
26 | ffi::PyComplexObject, |
27 | pyobject_native_static_type_object!(ffi::PyComplex_Type), |
28 | #checkfunction=ffi::PyComplex_Check |
29 | ); |
30 | |
31 | impl PyComplex { |
32 | /// Creates a new `PyComplex` from the given real and imaginary values. |
33 | pub fn from_doubles(py: Python<'_>, real: c_double, imag: c_double) -> Bound<'_, PyComplex> { |
34 | use crate::ffi_ptr_ext::FfiPtrExt; |
35 | unsafe { |
36 | ffiBound<'_, PyAny>::PyComplex_FromDoubles(real, imag) |
37 | .assume_owned(py) |
38 | .downcast_into_unchecked() |
39 | } |
40 | } |
41 | |
42 | /// Deprecated name for [`PyComplex::from_doubles`]. |
43 | #[deprecated (since = "0.23.0" , note = "renamed to `PyComplex::from_doubles`" )] |
44 | #[inline ] |
45 | pub fn from_doubles_bound( |
46 | py: Python<'_>, |
47 | real: c_double, |
48 | imag: c_double, |
49 | ) -> Bound<'_, PyComplex> { |
50 | Self::from_doubles(py, real, imag) |
51 | } |
52 | } |
53 | |
54 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
55 | mod not_limited_impls { |
56 | use crate::Borrowed; |
57 | |
58 | use super::*; |
59 | use std::ops::{Add, Div, Mul, Neg, Sub}; |
60 | |
61 | macro_rules! bin_ops { |
62 | ($trait:ident, $fn:ident, $op:tt) => { |
63 | impl<'py> $trait for Borrowed<'_, 'py, PyComplex> { |
64 | type Output = Bound<'py, PyComplex>; |
65 | fn $fn(self, other: Self) -> Self::Output { |
66 | PyAnyMethods::$fn(self.as_any(), other) |
67 | .downcast_into().expect( |
68 | concat!("Complex method " , |
69 | stringify!($fn), |
70 | " failed." ) |
71 | ) |
72 | } |
73 | } |
74 | |
75 | impl<'py> $trait for &Bound<'py, PyComplex> { |
76 | type Output = Bound<'py, PyComplex>; |
77 | fn $fn(self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> { |
78 | self.as_borrowed() $op other.as_borrowed() |
79 | } |
80 | } |
81 | |
82 | impl<'py> $trait<Bound<'py, PyComplex>> for &Bound<'py, PyComplex> { |
83 | type Output = Bound<'py, PyComplex>; |
84 | fn $fn(self, other: Bound<'py, PyComplex>) -> Bound<'py, PyComplex> { |
85 | self.as_borrowed() $op other.as_borrowed() |
86 | } |
87 | } |
88 | |
89 | impl<'py> $trait for Bound<'py, PyComplex> { |
90 | type Output = Bound<'py, PyComplex>; |
91 | fn $fn(self, other: Bound<'py, PyComplex>) -> Bound<'py, PyComplex> { |
92 | self.as_borrowed() $op other.as_borrowed() |
93 | } |
94 | } |
95 | |
96 | impl<'py> $trait<&Self> for Bound<'py, PyComplex> { |
97 | type Output = Bound<'py, PyComplex>; |
98 | fn $fn(self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> { |
99 | self.as_borrowed() $op other.as_borrowed() |
100 | } |
101 | } |
102 | }; |
103 | } |
104 | |
105 | bin_ops!(Add, add, +); |
106 | bin_ops!(Sub, sub, -); |
107 | bin_ops!(Mul, mul, *); |
108 | bin_ops!(Div, div, /); |
109 | |
110 | impl<'py> Neg for Borrowed<'_, 'py, PyComplex> { |
111 | type Output = Bound<'py, PyComplex>; |
112 | fn neg(self) -> Self::Output { |
113 | PyAnyMethods::neg(self.as_any()) |
114 | .downcast_into() |
115 | .expect("Complex method __neg__ failed." ) |
116 | } |
117 | } |
118 | |
119 | impl<'py> Neg for &Bound<'py, PyComplex> { |
120 | type Output = Bound<'py, PyComplex>; |
121 | fn neg(self) -> Bound<'py, PyComplex> { |
122 | -self.as_borrowed() |
123 | } |
124 | } |
125 | |
126 | impl<'py> Neg for Bound<'py, PyComplex> { |
127 | type Output = Bound<'py, PyComplex>; |
128 | fn neg(self) -> Bound<'py, PyComplex> { |
129 | -self.as_borrowed() |
130 | } |
131 | } |
132 | |
133 | #[cfg (test)] |
134 | mod tests { |
135 | use super::PyComplex; |
136 | use crate::{types::complex::PyComplexMethods, Python}; |
137 | use assert_approx_eq::assert_approx_eq; |
138 | |
139 | #[test ] |
140 | fn test_add() { |
141 | Python::with_gil(|py| { |
142 | let l = PyComplex::from_doubles(py, 3.0, 1.2); |
143 | let r = PyComplex::from_doubles(py, 1.0, 2.6); |
144 | let res = l + r; |
145 | assert_approx_eq!(res.real(), 4.0); |
146 | assert_approx_eq!(res.imag(), 3.8); |
147 | }); |
148 | } |
149 | |
150 | #[test ] |
151 | fn test_sub() { |
152 | Python::with_gil(|py| { |
153 | let l = PyComplex::from_doubles(py, 3.0, 1.2); |
154 | let r = PyComplex::from_doubles(py, 1.0, 2.6); |
155 | let res = l - r; |
156 | assert_approx_eq!(res.real(), 2.0); |
157 | assert_approx_eq!(res.imag(), -1.4); |
158 | }); |
159 | } |
160 | |
161 | #[test ] |
162 | fn test_mul() { |
163 | Python::with_gil(|py| { |
164 | let l = PyComplex::from_doubles(py, 3.0, 1.2); |
165 | let r = PyComplex::from_doubles(py, 1.0, 2.6); |
166 | let res = l * r; |
167 | assert_approx_eq!(res.real(), -0.12); |
168 | assert_approx_eq!(res.imag(), 9.0); |
169 | }); |
170 | } |
171 | |
172 | #[test ] |
173 | fn test_div() { |
174 | Python::with_gil(|py| { |
175 | let l = PyComplex::from_doubles(py, 3.0, 1.2); |
176 | let r = PyComplex::from_doubles(py, 1.0, 2.6); |
177 | let res = l / r; |
178 | assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); |
179 | assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); |
180 | }); |
181 | } |
182 | |
183 | #[test ] |
184 | fn test_neg() { |
185 | Python::with_gil(|py| { |
186 | let val = PyComplex::from_doubles(py, 3.0, 1.2); |
187 | let res = -val; |
188 | assert_approx_eq!(res.real(), -3.0); |
189 | assert_approx_eq!(res.imag(), -1.2); |
190 | }); |
191 | } |
192 | |
193 | #[test ] |
194 | fn test_abs() { |
195 | Python::with_gil(|py| { |
196 | let val = PyComplex::from_doubles(py, 3.0, 1.2); |
197 | assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); |
198 | }); |
199 | } |
200 | |
201 | #[test ] |
202 | fn test_pow() { |
203 | Python::with_gil(|py| { |
204 | let l = PyComplex::from_doubles(py, 3.0, 1.2); |
205 | let r = PyComplex::from_doubles(py, 1.2, 2.6); |
206 | let val = l.pow(&r); |
207 | assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); |
208 | assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); |
209 | }); |
210 | } |
211 | } |
212 | } |
213 | |
214 | /// Implementation of functionality for [`PyComplex`]. |
215 | /// |
216 | /// These methods are defined for the `Bound<'py, PyComplex>` smart pointer, so to use method call |
217 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
218 | /// `arbitrary_self_types`. |
219 | #[doc (alias = "PyComplex" )] |
220 | pub trait PyComplexMethods<'py>: crate::sealed::Sealed { |
221 | /// Returns the real part of the complex number. |
222 | fn real(&self) -> c_double; |
223 | /// Returns the imaginary part of the complex number. |
224 | fn imag(&self) -> c_double; |
225 | /// Returns `|self|`. |
226 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
227 | fn abs(&self) -> c_double; |
228 | /// Returns `self` raised to the power of `other`. |
229 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
230 | fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex>; |
231 | } |
232 | |
233 | impl<'py> PyComplexMethods<'py> for Bound<'py, PyComplex> { |
234 | fn real(&self) -> c_double { |
235 | unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) } |
236 | } |
237 | |
238 | fn imag(&self) -> c_double { |
239 | unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) } |
240 | } |
241 | |
242 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
243 | fn abs(&self) -> c_double { |
244 | PyAnyMethods::abs(self.as_any()) |
245 | .downcast_into() |
246 | .expect("Complex method __abs__ failed." ) |
247 | .extract() |
248 | .expect("Failed to extract to c double." ) |
249 | } |
250 | |
251 | #[cfg (not(any(Py_LIMITED_API, PyPy, GraalPy)))] |
252 | fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> { |
253 | Python::with_gil(|py| { |
254 | PyAnyMethods::pow(self.as_any(), other, py.None()) |
255 | .downcast_into() |
256 | .expect("Complex method __pow__ failed." ) |
257 | }) |
258 | } |
259 | } |
260 | |
261 | #[cfg (test)] |
262 | mod tests { |
263 | use super::PyComplex; |
264 | use crate::{types::complex::PyComplexMethods, Python}; |
265 | use assert_approx_eq::assert_approx_eq; |
266 | |
267 | #[test ] |
268 | fn test_from_double() { |
269 | use assert_approx_eq::assert_approx_eq; |
270 | |
271 | Python::with_gil(|py| { |
272 | let complex = PyComplex::from_doubles(py, 3.0, 1.2); |
273 | assert_approx_eq!(complex.real(), 3.0); |
274 | assert_approx_eq!(complex.imag(), 1.2); |
275 | }); |
276 | } |
277 | } |
278 | |