1use ferray_core::Array;
6use ferray_core::dimension::Dimension;
7use ferray_core::dtype::Element;
8use ferray_core::error::FerrayResult;
9use num_complex::Complex;
10use num_traits::Float;
11
12pub fn real<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<T, D>>
16where
17 T: Element + Float,
18 Complex<T>: Element,
19 D: Dimension,
20{
21 let data: Vec<T> = input.iter().map(|c| c.re).collect();
22 Array::from_vec(input.dim().clone(), data)
23}
24
25pub fn imag<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<T, D>>
27where
28 T: Element + Float,
29 Complex<T>: Element,
30 D: Dimension,
31{
32 let data: Vec<T> = input.iter().map(|c| c.im).collect();
33 Array::from_vec(input.dim().clone(), data)
34}
35
36pub fn conj<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
38where
39 T: Element + Float,
40 Complex<T>: Element,
41 D: Dimension,
42{
43 let data: Vec<Complex<T>> = input.iter().map(num_complex::Complex::conj).collect();
44 Array::from_vec(input.dim().clone(), data)
45}
46
47pub fn conjugate<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
49where
50 T: Element + Float,
51 Complex<T>: Element,
52 D: Dimension,
53{
54 conj(input)
55}
56
57pub fn angle<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<T, D>>
61where
62 T: Element + Float,
63 Complex<T>: Element,
64 D: Dimension,
65{
66 let data: Vec<T> = input.iter().map(|c| c.im.atan2(c.re)).collect();
67 Array::from_vec(input.dim().clone(), data)
68}
69
70pub fn abs<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<T, D>>
74where
75 T: Element + Float,
76 Complex<T>: Element,
77 D: Dimension,
78{
79 let data: Vec<T> = input
80 .iter()
81 .map(|c| (c.re * c.re + c.im * c.im).sqrt())
82 .collect();
83 Array::from_vec(input.dim().clone(), data)
84}
85
86pub fn iscomplex<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<bool, D>>
96where
97 T: Element + Float,
98 Complex<T>: Element,
99 D: Dimension,
100{
101 let zero = <T as num_traits::Zero>::zero();
102 let data: Vec<bool> = input.iter().map(|c| c.im != zero).collect();
103 Array::from_vec(input.dim().clone(), data)
104}
105
106pub fn iscomplex_real<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
112where
113 T: Element,
114 D: Dimension,
115{
116 let data = vec![false; input.size()];
117 Array::from_vec(input.dim().clone(), data)
118}
119
120pub fn isreal<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<bool, D>>
125where
126 T: Element + Float,
127 Complex<T>: Element,
128 D: Dimension,
129{
130 let zero = <T as num_traits::Zero>::zero();
131 let data: Vec<bool> = input.iter().map(|c| c.im == zero).collect();
132 Array::from_vec(input.dim().clone(), data)
133}
134
135pub fn isreal_real<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
137where
138 T: Element,
139 D: Dimension,
140{
141 let data = vec![true; input.size()];
142 Array::from_vec(input.dim().clone(), data)
143}
144
145pub fn iscomplexobj<T, D>(_: &Array<T, D>) -> bool
150where
151 T: Element,
152 D: Dimension,
153{
154 T::dtype().is_complex()
155}
156
157pub fn isrealobj<T, D>(a: &Array<T, D>) -> bool
161where
162 T: Element,
163 D: Dimension,
164{
165 !iscomplexobj(a)
166}
167
168pub fn isscalar<T, D>(a: &Array<T, D>) -> bool
174where
175 T: Element,
176 D: Dimension,
177{
178 a.shape().is_empty()
179}
180
181macro_rules! complex_unary {
194 ($fn_name:ident, $method:ident, $doc:expr) => {
195 #[doc = $doc]
196 pub fn $fn_name<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
197 where
198 T: Element + Float,
199 Complex<T>: Element,
200 D: Dimension,
201 {
202 let data: Vec<Complex<T>> = input.iter().map(|z| z.$method()).collect();
203 Array::from_vec(input.dim().clone(), data)
204 }
205 };
206}
207
208complex_unary!(
209 sin_complex,
210 sin,
211 "Element-wise complex sin: sin(z) = sin(z.re)cosh(z.im) + i cos(z.re)sinh(z.im)."
212);
213complex_unary!(cos_complex, cos, "Element-wise complex cos.");
214complex_unary!(tan_complex, tan, "Element-wise complex tan.");
215complex_unary!(sinh_complex, sinh, "Element-wise complex sinh.");
216complex_unary!(cosh_complex, cosh, "Element-wise complex cosh.");
217complex_unary!(tanh_complex, tanh, "Element-wise complex tanh.");
218complex_unary!(asin_complex, asin, "Element-wise complex arcsin.");
219complex_unary!(acos_complex, acos, "Element-wise complex arccos.");
220complex_unary!(atan_complex, atan, "Element-wise complex arctan.");
221complex_unary!(asinh_complex, asinh, "Element-wise complex arcsinh.");
222complex_unary!(acosh_complex, acosh, "Element-wise complex arccosh.");
223complex_unary!(atanh_complex, atanh, "Element-wise complex arctanh.");
224complex_unary!(
225 exp_complex,
226 exp,
227 "Element-wise complex exp: exp(z) = exp(z.re) (cos(z.im) + i sin(z.im))."
228);
229complex_unary!(
230 ln_complex,
231 ln,
232 "Element-wise complex natural log: log(z) = log|z| + i arg(z), branch cut along negative real axis."
233);
234complex_unary!(
235 sqrt_complex,
236 sqrt,
237 "Element-wise complex sqrt; principal branch (Re(sqrt(z)) >= 0)."
238);
239
240pub fn log2_complex<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
242where
243 T: Element + Float,
244 Complex<T>: Element,
245 D: Dimension,
246{
247 let one = <T as num_traits::One>::one();
248 let ln2: T = <T as num_traits::NumCast>::from(core::f64::consts::LN_2).unwrap();
249 let inv_ln2 = one / ln2;
250 let data: Vec<Complex<T>> = input
251 .iter()
252 .map(|z| {
253 let l = z.ln();
254 Complex::new(l.re * inv_ln2, l.im * inv_ln2)
255 })
256 .collect();
257 Array::from_vec(input.dim().clone(), data)
258}
259
260pub fn log10_complex<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
262where
263 T: Element + Float,
264 Complex<T>: Element,
265 D: Dimension,
266{
267 let one = <T as num_traits::One>::one();
268 let ln10: T = <T as num_traits::NumCast>::from(core::f64::consts::LN_10).unwrap();
269 let inv_ln10 = one / ln10;
270 let data: Vec<Complex<T>> = input
271 .iter()
272 .map(|z| {
273 let l = z.ln();
274 Complex::new(l.re * inv_ln10, l.im * inv_ln10)
275 })
276 .collect();
277 Array::from_vec(input.dim().clone(), data)
278}
279
280pub fn expm1_complex<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
285where
286 T: Element + Float,
287 Complex<T>: Element,
288 D: Dimension,
289{
290 let one = <T as num_traits::One>::one();
291 let two = one + one;
292 let half: T = <T as num_traits::NumCast>::from(0.5_f64).unwrap();
293 let data: Vec<Complex<T>> = input
294 .iter()
295 .map(|z| {
296 let er_m1 = z.re.exp_m1();
299 let er = er_m1 + one;
300 let cos_im = z.im.cos();
301 let sin_im = z.im.sin();
302 let s = (z.im * half).sin();
304 let cos_im_m1 = -two * s * s;
305 Complex::new(er_m1 * cos_im + cos_im_m1, er * sin_im)
306 })
307 .collect();
308 Array::from_vec(input.dim().clone(), data)
309}
310
311pub fn log1p_complex<T, D>(input: &Array<Complex<T>, D>) -> FerrayResult<Array<Complex<T>, D>>
317where
318 T: Element + Float,
319 Complex<T>: Element,
320 D: Dimension,
321{
322 let one = <T as num_traits::One>::one();
323 let zero = <T as num_traits::Zero>::zero();
324 let two = one + one;
325 let half: T = <T as num_traits::NumCast>::from(0.5_f64).unwrap();
326 let small: T = <T as num_traits::NumCast>::from(0.5_f64).unwrap();
327 let data: Vec<Complex<T>> = input
328 .iter()
329 .map(|z| {
330 if z.re.abs() < small && z.im.abs() < small {
331 let u = z.re * (two + z.re) + z.im * z.im;
334 let half_log = half * u.ln_1p();
335 let arg = z.im.atan2(one + z.re);
336 Complex::new(half_log, arg)
337 } else {
338 (Complex::new(one, zero) + *z).ln()
339 }
340 })
341 .collect();
342 Array::from_vec(input.dim().clone(), data)
343}
344
345pub fn power_complex<T, D>(
348 base: &Array<Complex<T>, D>,
349 exponent: Complex<T>,
350) -> FerrayResult<Array<Complex<T>, D>>
351where
352 T: Element + Float,
353 Complex<T>: Element,
354 D: Dimension,
355{
356 let data: Vec<Complex<T>> = base.iter().map(|z| z.powc(exponent)).collect();
357 Array::from_vec(base.dim().clone(), data)
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use ferray_core::dimension::Ix1;
364 use num_complex::Complex64;
365
366 fn arr1_c64(data: Vec<Complex64>) -> Array<Complex64, Ix1> {
367 let n = data.len();
368 Array::from_vec(Ix1::new([n]), data).unwrap()
369 }
370
371 #[test]
372 fn test_real() {
373 let a = arr1_c64(vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)]);
374 let r = real(&a).unwrap();
375 assert_eq!(r.as_slice().unwrap(), &[1.0, 3.0]);
376 }
377
378 #[test]
379 fn test_imag() {
380 let a = arr1_c64(vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)]);
381 let r = imag(&a).unwrap();
382 assert_eq!(r.as_slice().unwrap(), &[2.0, 4.0]);
383 }
384
385 #[test]
386 fn test_conj() {
387 let a = arr1_c64(vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, -4.0)]);
388 let r = conj(&a).unwrap();
389 let s = r.as_slice().unwrap();
390 assert_eq!(s[0], Complex64::new(1.0, -2.0));
391 assert_eq!(s[1], Complex64::new(3.0, 4.0));
392 }
393
394 #[test]
395 fn test_conjugate_alias() {
396 let a = arr1_c64(vec![Complex64::new(1.0, 2.0)]);
397 let r = conjugate(&a).unwrap();
398 assert_eq!(r.as_slice().unwrap()[0], Complex64::new(1.0, -2.0));
399 }
400
401 #[test]
402 fn test_angle() {
403 let a = arr1_c64(vec![
404 Complex64::new(1.0, 0.0),
405 Complex64::new(0.0, 1.0),
406 Complex64::new(-1.0, 0.0),
407 ]);
408 let r = angle(&a).unwrap();
409 let s = r.as_slice().unwrap();
410 assert!((s[0] - 0.0).abs() < 1e-12);
411 assert!((s[1] - std::f64::consts::FRAC_PI_2).abs() < 1e-12);
412 assert!((s[2] - std::f64::consts::PI).abs() < 1e-12);
413 }
414
415 #[test]
416 fn test_abs() {
417 let a = arr1_c64(vec![Complex64::new(3.0, 4.0), Complex64::new(0.0, 1.0)]);
418 let r = abs(&a).unwrap();
419 let s = r.as_slice().unwrap();
420 assert!((s[0] - 5.0).abs() < 1e-12);
421 assert!((s[1] - 1.0).abs() < 1e-12);
422 }
423
424 #[test]
425 fn test_iscomplex_complex_input() {
426 let a = arr1_c64(vec![
427 Complex64::new(1.0, 0.0),
428 Complex64::new(2.0, 1.5),
429 Complex64::new(0.0, 0.0),
430 ]);
431 let r = iscomplex(&a).unwrap();
432 assert_eq!(r.as_slice().unwrap(), &[false, true, false]);
433 }
434
435 #[test]
436 fn test_isreal_complex_input() {
437 let a = arr1_c64(vec![Complex64::new(1.0, 0.0), Complex64::new(2.0, 1.5)]);
438 let r = isreal(&a).unwrap();
439 assert_eq!(r.as_slice().unwrap(), &[true, false]);
440 }
441
442 #[test]
443 fn test_iscomplex_real_input() {
444 let a: Array<f64, Ix1> = Array::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
445 let r = iscomplex_real(&a).unwrap();
446 assert_eq!(r.as_slice().unwrap(), &[false, false, false]);
447 }
448
449 #[test]
450 fn test_isreal_real_input() {
451 let a: Array<f64, Ix1> = Array::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
452 let r = isreal_real(&a).unwrap();
453 assert_eq!(r.as_slice().unwrap(), &[true, true, true]);
454 }
455
456 #[test]
457 fn test_iscomplexobj_isrealobj() {
458 let c = arr1_c64(vec![Complex64::new(1.0, 2.0)]);
459 assert!(iscomplexobj(&c));
460 assert!(!isrealobj(&c));
461
462 let r: Array<f64, Ix1> = Array::from_vec(Ix1::new([2]), vec![1.0, 2.0]).unwrap();
463 assert!(!iscomplexobj(&r));
464 assert!(isrealobj(&r));
465 }
466
467 fn approx_eq_c(a: Complex64, b: Complex64, eps: f64) -> bool {
468 (a.re - b.re).abs() <= eps && (a.im - b.im).abs() <= eps
469 }
470
471 #[test]
472 fn test_sin_complex_matches_identity() {
473 let arr = arr1_c64(vec![Complex64::new(0.0, 1.0), Complex64::new(1.0, 1.0)]);
475 let out = sin_complex(&arr).unwrap();
476 let v0 = out.iter().next().copied().unwrap();
477 assert!(approx_eq_c(
478 v0,
479 Complex64::new(0.0, 1.1752011936438014),
480 1e-10
481 ));
482 }
483
484 #[test]
485 fn test_cos_complex_matches_identity() {
486 let arr = arr1_c64(vec![Complex64::new(0.0, 1.0)]);
488 let out = cos_complex(&arr).unwrap();
489 let v0 = out.iter().next().copied().unwrap();
490 assert!(approx_eq_c(
491 v0,
492 Complex64::new(1.5430806348152437, 0.0),
493 1e-10
494 ));
495 }
496
497 #[test]
498 fn test_exp_complex_eulers_identity() {
499 let arr = arr1_c64(vec![Complex64::new(0.0, std::f64::consts::PI)]);
501 let out = exp_complex(&arr).unwrap();
502 let v0 = out.iter().next().copied().unwrap();
503 assert!(approx_eq_c(v0, Complex64::new(-1.0, 0.0), 1e-12));
504 }
505
506 #[test]
507 fn test_ln_complex_inverse_of_exp() {
508 let z = Complex64::new(0.7, 1.3);
509 let arr = arr1_c64(vec![z.exp()]);
510 let out = ln_complex(&arr).unwrap();
511 let v = out.iter().next().copied().unwrap();
512 assert!(approx_eq_c(v, z, 1e-12));
513 }
514
515 #[test]
516 fn test_sqrt_complex_principal_branch() {
517 let arr = arr1_c64(vec![Complex64::new(0.0, 1.0)]);
519 let out = sqrt_complex(&arr).unwrap();
520 let v = out.iter().next().copied().unwrap();
521 let inv_sqrt2 = 1.0_f64 / 2.0_f64.sqrt();
522 assert!(approx_eq_c(v, Complex64::new(inv_sqrt2, inv_sqrt2), 1e-12));
523 }
524
525 #[test]
526 fn test_log2_complex() {
527 let arr = arr1_c64(vec![Complex64::new(2.0, 0.0)]);
529 let out = log2_complex(&arr).unwrap();
530 let v = out.iter().next().copied().unwrap();
531 assert!(approx_eq_c(v, Complex64::new(1.0, 0.0), 1e-12));
532 }
533
534 #[test]
535 fn test_log10_complex() {
536 let arr = arr1_c64(vec![Complex64::new(100.0, 0.0)]);
538 let out = log10_complex(&arr).unwrap();
539 let v = out.iter().next().copied().unwrap();
540 assert!(approx_eq_c(v, Complex64::new(2.0, 0.0), 1e-12));
541 }
542
543 #[test]
544 fn test_expm1_complex_small_arg_precision() {
545 let z = Complex64::new(1e-12, 1e-12);
553 let arr = arr1_c64(vec![z]);
554 let out = expm1_complex(&arr).unwrap();
555 let v = out.iter().next().copied().unwrap();
556 let rel_err = (v - z).norm() / z.norm();
559 assert!(
560 rel_err < 1e-4,
561 "expm1 small-arg rel_err = {rel_err:e}, v = {v:?}"
562 );
563 }
564
565 #[test]
566 fn test_log1p_complex_small_arg_precision() {
567 let z = Complex64::new(1e-10, 1e-10);
569 let arr = arr1_c64(vec![z]);
570 let out = log1p_complex(&arr).unwrap();
571 let v = out.iter().next().copied().unwrap();
572 let rel_err = (v - z).norm() / z.norm();
573 assert!(
574 rel_err < 1e-4,
575 "log1p small-arg rel_err = {rel_err:e}, v = {v:?}"
576 );
577 }
578
579 #[test]
580 fn test_expm1_complex_moderate_arg_matches_naive() {
581 let z = Complex64::new(0.5, 0.3);
583 let arr = arr1_c64(vec![z]);
584 let out = expm1_complex(&arr).unwrap();
585 let v = out.iter().next().copied().unwrap();
586 let expected = z.exp() - Complex64::new(1.0, 0.0);
587 assert!(approx_eq_c(v, expected, 1e-12));
588 }
589
590 #[test]
591 fn test_log1p_complex_moderate_arg_matches_naive() {
592 let z = Complex64::new(0.4, 0.2);
593 let arr = arr1_c64(vec![z]);
594 let out = log1p_complex(&arr).unwrap();
595 let v = out.iter().next().copied().unwrap();
596 let expected = (Complex64::new(1.0, 0.0) + z).ln();
597 assert!(approx_eq_c(v, expected, 1e-12));
598 }
599
600 #[test]
601 fn test_power_complex_integer_exponent() {
602 let arr = arr1_c64(vec![Complex64::new(1.0, 1.0)]);
604 let out = power_complex(&arr, Complex64::new(2.0, 0.0)).unwrap();
605 let v = out.iter().next().copied().unwrap();
606 assert!(approx_eq_c(v, Complex64::new(0.0, 2.0), 1e-12));
607 }
608
609 #[test]
610 fn test_tanh_complex_round_trip() {
611 let z = Complex64::new(0.5, 0.3);
613 let arr = arr1_c64(vec![z]);
614 let t = tanh_complex(&arr).unwrap();
615 let r = atanh_complex(&t).unwrap();
616 let v = r.iter().next().copied().unwrap();
617 assert!(approx_eq_c(v, z, 1e-12));
618 }
619
620 #[test]
621 fn test_isscalar_zero_d() {
622 use ferray_core::dimension::IxDyn;
623 let scalar: Array<f64, IxDyn> = Array::from_vec(IxDyn::new(&[]), vec![2.5]).unwrap();
624 assert!(isscalar(&scalar));
625
626 let vec: Array<f64, Ix1> = Array::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
627 assert!(!isscalar(&vec));
628 }
629}