1#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
2use crate::py_result_ext::PyResultExt;
3#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
4use crate::types::any::PyAnyMethods;
5use crate::{ffi, Bound, PyAny, Python};
6use std::ffi::c_double;
7
8#[repr(transparent)]
22pub struct PyComplex(PyAny);
23
24pyobject_subclassable_native_type!(PyComplex, ffi::PyComplexObject);
25
26pyobject_native_type!(
27 PyComplex,
28 ffi::PyComplexObject,
29 pyobject_native_static_type_object!(ffi::PyComplex_Type),
30 "builtins",
31 "complex",
32 #checkfunction=ffi::PyComplex_Check
33);
34
35impl PyComplex {
36 pub fn from_doubles(py: Python<'_>, real: c_double, imag: c_double) -> Bound<'_, PyComplex> {
38 use crate::ffi_ptr_ext::FfiPtrExt;
39 unsafe {
40 ffi::PyComplex_FromDoubles(real, imag)
41 .assume_owned(py)
42 .cast_into_unchecked()
43 }
44 }
45}
46
47#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
48mod not_limited_impls {
49 use crate::Borrowed;
50
51 use super::*;
52 use std::ops::{Add, Div, Mul, Neg, Sub};
53
54 macro_rules! bin_ops {
55 ($trait:ident, $fn:ident, $op:tt) => {
56 impl<'py> $trait for Borrowed<'_, 'py, PyComplex> {
57 type Output = Bound<'py, PyComplex>;
58 fn $fn(self, other: Self) -> Self::Output {
59 PyAnyMethods::$fn(self.as_any(), other)
60 .cast_into().expect(
61 concat!("Complex method ",
62 stringify!($fn),
63 " failed.")
64 )
65 }
66 }
67
68 impl<'py> $trait for &Bound<'py, PyComplex> {
69 type Output = Bound<'py, PyComplex>;
70 fn $fn(self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
71 self.as_borrowed() $op other.as_borrowed()
72 }
73 }
74
75 impl<'py> $trait<Bound<'py, PyComplex>> 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 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<&Self> 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 }
97
98 bin_ops!(Add, add, +);
99 bin_ops!(Sub, sub, -);
100 bin_ops!(Mul, mul, *);
101 bin_ops!(Div, div, /);
102
103 impl<'py> Neg for Borrowed<'_, 'py, PyComplex> {
104 type Output = Bound<'py, PyComplex>;
105 fn neg(self) -> Self::Output {
106 PyAnyMethods::neg(self.as_any())
107 .cast_into()
108 .expect("Complex method __neg__ failed.")
109 }
110 }
111
112 impl<'py> Neg for &Bound<'py, PyComplex> {
113 type Output = Bound<'py, PyComplex>;
114 fn neg(self) -> Bound<'py, PyComplex> {
115 -self.as_borrowed()
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 #[cfg(test)]
127 mod tests {
128 use super::PyComplex;
129 use crate::{types::complex::PyComplexMethods, Python};
130 use assert_approx_eq::assert_approx_eq;
131
132 #[test]
133 fn test_add() {
134 Python::attach(|py| {
135 let l = PyComplex::from_doubles(py, 3.0, 1.2);
136 let r = PyComplex::from_doubles(py, 1.0, 2.6);
137 let res = l + r;
138 assert_approx_eq!(res.real(), 4.0);
139 assert_approx_eq!(res.imag(), 3.8);
140 });
141 }
142
143 #[test]
144 fn test_sub() {
145 Python::attach(|py| {
146 let l = PyComplex::from_doubles(py, 3.0, 1.2);
147 let r = PyComplex::from_doubles(py, 1.0, 2.6);
148 let res = l - r;
149 assert_approx_eq!(res.real(), 2.0);
150 assert_approx_eq!(res.imag(), -1.4);
151 });
152 }
153
154 #[test]
155 fn test_mul() {
156 Python::attach(|py| {
157 let l = PyComplex::from_doubles(py, 3.0, 1.2);
158 let r = PyComplex::from_doubles(py, 1.0, 2.6);
159 let res = l * r;
160 assert_approx_eq!(res.real(), -0.12);
161 assert_approx_eq!(res.imag(), 9.0);
162 });
163 }
164
165 #[test]
166 fn test_div() {
167 Python::attach(|py| {
168 let l = PyComplex::from_doubles(py, 3.0, 1.2);
169 let r = PyComplex::from_doubles(py, 1.0, 2.6);
170 let res = l / r;
171 assert_approx_eq!(res.real(), 0.788_659_793_814_432_9);
172 assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7);
173 });
174 }
175
176 #[test]
177 fn test_neg() {
178 Python::attach(|py| {
179 let val = PyComplex::from_doubles(py, 3.0, 1.2);
180 let res = -val;
181 assert_approx_eq!(res.real(), -3.0);
182 assert_approx_eq!(res.imag(), -1.2);
183 });
184 }
185
186 #[test]
187 fn test_abs() {
188 Python::attach(|py| {
189 let val = PyComplex::from_doubles(py, 3.0, 1.2);
190 assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2);
191 });
192 }
193
194 #[test]
195 fn test_pow() {
196 Python::attach(|py| {
197 let l = PyComplex::from_doubles(py, 3.0, 1.2);
198 let r = PyComplex::from_doubles(py, 1.2, 2.6);
199 let val = l.pow(&r);
200 assert_approx_eq!(val.real(), -1.419_309_997_016_603_7);
201 assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6);
202 });
203 }
204 }
205}
206
207#[doc(alias = "PyComplex")]
213pub trait PyComplexMethods<'py>: crate::sealed::Sealed {
214 fn real(&self) -> c_double;
216 fn imag(&self) -> c_double;
218 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
220 fn abs(&self) -> c_double;
221 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
223 fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex>;
224}
225
226impl<'py> PyComplexMethods<'py> for Bound<'py, PyComplex> {
227 fn real(&self) -> c_double {
228 unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) }
229 }
230
231 fn imag(&self) -> c_double {
232 unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) }
233 }
234
235 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
236 fn abs(&self) -> c_double {
237 PyAnyMethods::abs(self.as_any())
238 .cast_into()
239 .expect("Complex method __abs__ failed.")
240 .extract()
241 .expect("Failed to extract to c double.")
242 }
243
244 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
245 fn pow(&self, other: &Bound<'py, PyComplex>) -> Bound<'py, PyComplex> {
246 Python::attach(|py| {
247 PyAnyMethods::pow(self.as_any(), other, py.None())
248 .cast_into()
249 .expect("Complex method __pow__ failed.")
250 })
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::PyComplex;
257 use crate::{types::complex::PyComplexMethods, Python};
258 use assert_approx_eq::assert_approx_eq;
259
260 #[test]
261 fn test_from_double() {
262 Python::attach(|py| {
263 let complex = PyComplex::from_doubles(py, 3.0, 1.2);
264 assert_approx_eq!(complex.real(), 3.0);
265 assert_approx_eq!(complex.imag(), 1.2);
266 });
267 }
268}