1#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
2use crate::py_result_ext::PyResultExt;
3#[cfg(feature = "gil-refs")]
4use crate::PyNativeType;
5use crate::{ffi, types::any::PyAnyMethods, Bound, PyAny, Python};
6use std::os::raw::c_double;
7
8#[repr(transparent)]
22pub struct PyComplex(PyAny);
23
24pyobject_native_type!(
25 PyComplex,
26 ffi::PyComplexObject,
27 pyobject_native_static_type_object!(ffi::PyComplex_Type),
28 #checkfunction=ffi::PyComplex_Check
29);
30
31impl PyComplex {
32 pub fn from_doubles_bound(
34 py: Python<'_>,
35 real: c_double,
36 imag: c_double,
37 ) -> Bound<'_, PyComplex> {
38 use crate::ffi_ptr_ext::FfiPtrExt;
39 unsafe {
40 ffi::PyComplex_FromDoubles(real, imag)
41 .assume_owned(py)
42 .downcast_into_unchecked()
43 }
44 }
45}
46
47#[cfg(feature = "gil-refs")]
48impl PyComplex {
49 #[deprecated(
51 since = "0.21.0",
52 note = "`PyComplex::from_doubles` will be replaced by `PyComplex::from_doubles_bound` in a future PyO3 version"
53 )]
54 pub fn from_doubles(py: Python<'_>, real: c_double, imag: c_double) -> &PyComplex {
55 Self::from_doubles_bound(py, real, imag).into_gil_ref()
56 }
57
58 pub fn real(&self) -> c_double {
60 self.as_borrowed().real()
61 }
62 pub fn imag(&self) -> c_double {
64 self.as_borrowed().imag()
65 }
66}
67
68#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
69mod not_limited_impls {
70 use crate::Borrowed;
71
72 use super::*;
73 use std::ops::{Add, Div, Mul, Neg, Sub};
74
75 #[cfg(feature = "gil-refs")]
76 impl PyComplex {
77 pub fn abs(&self) -> c_double {
79 self.as_borrowed().abs()
80 }
81 pub fn pow<'py>(&'py self, other: &'py PyComplex) -> &'py PyComplex {
83 self.as_borrowed().pow(&other.as_borrowed()).into_gil_ref()
84 }
85 }
86
87 macro_rules! bin_ops {
88 ($trait:ident, $fn:ident, $op:tt) => {
89 impl<'py> $trait for Borrowed<'_, 'py, PyComplex> {
90 type Output = Bound<'py, PyComplex>;
91 fn $fn(self, other: Self) -> Self::Output {
92 PyAnyMethods::$fn(self.as_any(), other)
93 .downcast_into().expect(
94 concat!("Complex method ",
95 stringify!($fn),
96 " failed.")
97 )
98 }
99 }
100
101 #[cfg(feature = "gil-refs")]
102 impl<'py> $trait for &'py PyComplex {
103 type Output = &'py PyComplex;
104 fn $fn(self, other: &'py PyComplex) -> &'py PyComplex {
105 (self.as_borrowed() $op other.as_borrowed()).into_gil_ref()
106 }
107 }
108
109 impl<'py> $trait for &Bound<'py, PyComplex> {
110 type Output = Bound<'py, PyComplex>;
111 fn $fn(self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
112 self.as_borrowed() $op other.as_borrowed()
113 }
114 }
115
116 impl<'py> $trait<Bound<'py, PyComplex>> for &Bound<'py, PyComplex> {
117 type Output = Bound<'py, PyComplex>;
118 fn $fn(self, other: Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
119 self.as_borrowed() $op other.as_borrowed()
120 }
121 }
122
123 impl<'py> $trait for Bound<'py, PyComplex> {
124 type Output = Bound<'py, PyComplex>;
125 fn $fn(self, other: Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
126 self.as_borrowed() $op other.as_borrowed()
127 }
128 }
129
130 impl<'py> $trait<&Self> for Bound<'py, PyComplex> {
131 type Output = Bound<'py, PyComplex>;
132 fn $fn(self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
133 self.as_borrowed() $op other.as_borrowed()
134 }
135 }
136 };
137 }
138
139 bin_ops!(Add, add, +);
140 bin_ops!(Sub, sub, -);
141 bin_ops!(Mul, mul, *);
142 bin_ops!(Div, div, /);
143
144 #[cfg(feature = "gil-refs")]
145 impl<'py> Neg for &'py PyComplex {
146 type Output = &'py PyComplex;
147 fn neg(self) -> &'py PyComplex {
148 (-self.as_borrowed()).into_gil_ref()
149 }
150 }
151
152 impl<'py> Neg for Borrowed<'_, 'py, PyComplex> {
153 type Output = Bound<'py, PyComplex>;
154 fn neg(self) -> Self::Output {
155 PyAnyMethods::neg(self.as_any())
156 .downcast_into()
157 .expect("Complex method __neg__ failed.")
158 }
159 }
160
161 impl<'py> Neg for &Bound<'py, PyComplex> {
162 type Output = Bound<'py, PyComplex>;
163 fn neg(self) -> Bound<'py, PyComplex> {
164 -self.as_borrowed()
165 }
166 }
167
168 impl<'py> Neg for Bound<'py, PyComplex> {
169 type Output = Bound<'py, PyComplex>;
170 fn neg(self) -> Bound<'py, PyComplex> {
171 -self.as_borrowed()
172 }
173 }
174
175 #[cfg(test)]
176 mod tests {
177 use super::PyComplex;
178 use crate::{types::complex::PyComplexMethods, Python};
179 use assert_approx_eq::assert_approx_eq;
180
181 #[test]
182 fn test_add() {
183 Python::with_gil(|py| {
184 let l = PyComplex::from_doubles_bound(py, 3.0, 1.2);
185 let r = PyComplex::from_doubles_bound(py, 1.0, 2.6);
186 let res = l + r;
187 assert_approx_eq!(res.real(), 4.0);
188 assert_approx_eq!(res.imag(), 3.8);
189 });
190 }
191
192 #[test]
193 fn test_sub() {
194 Python::with_gil(|py| {
195 let l = PyComplex::from_doubles_bound(py, 3.0, 1.2);
196 let r = PyComplex::from_doubles_bound(py, 1.0, 2.6);
197 let res = l - r;
198 assert_approx_eq!(res.real(), 2.0);
199 assert_approx_eq!(res.imag(), -1.4);
200 });
201 }
202
203 #[test]
204 fn test_mul() {
205 Python::with_gil(|py| {
206 let l = PyComplex::from_doubles_bound(py, 3.0, 1.2);
207 let r = PyComplex::from_doubles_bound(py, 1.0, 2.6);
208 let res = l * r;
209 assert_approx_eq!(res.real(), -0.12);
210 assert_approx_eq!(res.imag(), 9.0);
211 });
212 }
213
214 #[test]
215 fn test_div() {
216 Python::with_gil(|py| {
217 let l = PyComplex::from_doubles_bound(py, 3.0, 1.2);
218 let r = PyComplex::from_doubles_bound(py, 1.0, 2.6);
219 let res = l / r;
220 assert_approx_eq!(res.real(), 0.788_659_793_814_432_9);
221 assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7);
222 });
223 }
224
225 #[test]
226 fn test_neg() {
227 Python::with_gil(|py| {
228 let val = PyComplex::from_doubles_bound(py, 3.0, 1.2);
229 let res = -val;
230 assert_approx_eq!(res.real(), -3.0);
231 assert_approx_eq!(res.imag(), -1.2);
232 });
233 }
234
235 #[test]
236 fn test_abs() {
237 Python::with_gil(|py| {
238 let val = PyComplex::from_doubles_bound(py, 3.0, 1.2);
239 assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2);
240 });
241 }
242
243 #[test]
244 fn test_pow() {
245 Python::with_gil(|py| {
246 let l = PyComplex::from_doubles_bound(py, 3.0, 1.2);
247 let r = PyComplex::from_doubles_bound(py, 1.2, 2.6);
248 let val = l.pow(&r);
249 assert_approx_eq!(val.real(), -1.419_309_997_016_603_7);
250 assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6);
251 });
252 }
253 }
254}
255
256#[doc(alias = "PyComplex")]
262pub trait PyComplexMethods<'py>: crate::sealed::Sealed {
263 fn real(&self) -> c_double;
265 fn imag(&self) -> c_double;
267 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
269 fn abs(&self) -> c_double;
270 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
272 fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex>;
273}
274
275impl<'py> PyComplexMethods<'py> for Bound<'py, PyComplex> {
276 fn real(&self) -> c_double {
277 unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) }
278 }
279
280 fn imag(&self) -> c_double {
281 unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) }
282 }
283
284 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
285 fn abs(&self) -> c_double {
286 PyAnyMethods::abs(self.as_any())
287 .downcast_into()
288 .expect("Complex method __abs__ failed.")
289 .extract()
290 .expect("Failed to extract to c double.")
291 }
292
293 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
294 fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
295 Python::with_gil(|py| {
296 PyAnyMethods::pow(self.as_any(), other, py.None())
297 .downcast_into()
298 .expect("Complex method __pow__ failed.")
299 })
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::PyComplex;
306 use crate::{types::complex::PyComplexMethods, Python};
307 use assert_approx_eq::assert_approx_eq;
308
309 #[test]
310 fn test_from_double() {
311 use assert_approx_eq::assert_approx_eq;
312
313 Python::with_gil(|py| {
314 let complex = PyComplex::from_doubles_bound(py, 3.0, 1.2);
315 assert_approx_eq!(complex.real(), 3.0);
316 assert_approx_eq!(complex.imag(), 1.2);
317 });
318 }
319}