Skip to main content

symplex/api/
expr_complex.rs

1//! Complex-analysis methods on [`Ex`]: `re`, `im`,
2//! `conjugate`, `arg`, polar form — plus the 0.2 special-function
3//! constructors (`si`, `ci`, `ei`, `li`, `zeta`, `polygamma`,
4//! `kronecker_delta`).
5//!
6//! # Realness is never assumed
7//!
8//! A bare symbol `x` is *not* treated as real.  `x.re()` returns the
9//! unevaluated node `re(x)` unless `x` carries a `Real` (or stronger)
10//! assumption, in which case `x.re() == x` and `x.im() == 0`.  Use
11//! [`Context::symbol_with`](crate::api::context::Context::symbol_with) or
12//! [`Ex::assume`](crate::api::expr::Ex::assume) to declare assumptions.
13//!
14//! Principal-branch semantics apply throughout: `arg(z) ∈ (−π, π]`.
15
16use crate::api::expr::{Ex, Expr, Numeric};
17use crate::base::assumptions::Props;
18
19// ═══════════════════════════════════════════════════════════════════════════
20// impl Expr<Numeric> — complex analysis
21// ═══════════════════════════════════════════════════════════════════════════
22
23impl Expr<Numeric> {
24    /// Real part `re(self)`.
25    ///
26    /// Evaluates at construction whenever the real part is determinable
27    /// (numbers, constants, symbols assumed real, sums, real scalings,
28    /// products/powers of fully decomposable factors, `exp`, `sin`, `cos`,
29    /// `sinh`, `cosh`, `ln`, …).  Otherwise an unevaluated `re(…)` node is
30    /// returned — never a silently-wrong answer.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use symplex::prelude::*;
36    ///
37    /// let ctx = Context::new();
38    /// let i = ctx.i_unit();
39    /// let z = &ctx.int(3) + &(&ctx.int(4) * &i);
40    /// assert_eq!(format!("{}", z.re()), "3");
41    ///
42    /// // Unknown symbols stay symbolic …
43    /// let w = ctx.symbol("w");
44    /// assert_eq!(format!("{}", w.re()), "re(w)");
45    /// assert_eq!(format!("{}", (&i * &w).re()), "-im(w)");
46    ///
47    /// // … unless assumed real.
48    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
49    /// assert_eq!(x.re(), x);
50    /// ```
51    #[must_use]
52    pub fn re(&self) -> Ex {
53        let id = self.inner.write().arena.re(self.raw_id());
54        self.wrap(id)
55    }
56
57    /// Imaginary part `im(self)` — a *real* quantity such that
58    /// `self = re(self) + i·im(self)`.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use symplex::prelude::*;
64    ///
65    /// let ctx = Context::new();
66    /// let i = ctx.i_unit();
67    /// let z = &ctx.int(3) + &(&ctx.int(4) * &i);
68    /// assert_eq!(format!("{}", z.im()), "4");
69    ///
70    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
71    /// assert!(x.im().is_zero_structural());
72    /// assert_eq!(format!("{}", (&x.exp() * &i).im()), "exp(x)");
73    /// ```
74    #[must_use]
75    pub fn im(&self) -> Ex {
76        let id = self.inner.write().arena.im(self.raw_id());
77        self.wrap(id)
78    }
79
80    /// Complex conjugate `conjugate(self)`.
81    ///
82    /// Distributes over sums, products and integer powers, and commutes
83    /// with real-analytic functions that have no branch cut off the real
84    /// axis (`exp`, `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh`, `Γ`,
85    /// `erf`, `erfc`, `ψ`, `ζ`, `Si`).  Functions with branch cuts (`ln`,
86    /// non-integer powers, inverse trig/hyperbolic, `W`, …) stay as
87    /// unevaluated `conjugate(…)` nodes unless the argument is provably
88    /// real.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use symplex::prelude::*;
94    ///
95    /// let ctx = Context::new();
96    /// let i = ctx.i_unit();
97    /// let z = &ctx.int(3) + &(&ctx.int(4) * &i);
98    /// assert_eq!(format!("{}", z.conjugate()), "-4*I + 3");
99    ///
100    /// let w = ctx.symbol("w");
101    /// assert_eq!(format!("{}", w.sin().conjugate()), "sin(conjugate(w))");
102    /// assert_eq!(w.conjugate().conjugate(), w);
103    /// ```
104    #[must_use]
105    pub fn conjugate(&self) -> Ex {
106        let id = self.inner.write().arena.conjugate(self.raw_id());
107        self.wrap(id)
108    }
109
110    /// Principal complex argument `arg(self) ∈ (−π, π]`.
111    ///
112    /// Positive reals give `0`, negative reals give `π`, `i` gives `π/2`,
113    /// and `a + bi` with real `a`, `b` gives `atan2(b, a)` (folded for
114    /// numeric arguments).  Positive real factors are discarded:
115    /// `arg(3·z) = arg(z)`.  When the sign or complex structure cannot be
116    /// determined the result is an unevaluated `arg(…)` node.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use symplex::prelude::*;
122    ///
123    /// let ctx = Context::new();
124    /// let z = &ctx.int(1) + &ctx.i_unit();
125    /// assert_eq!(format!("{}", z.arg()), "1/4*pi");
126    /// assert_eq!(format!("{}", ctx.int(-2).arg()), "pi");
127    ///
128    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
129    /// assert_eq!(format!("{}", x.arg()), "arg(x)"); // sign unknown
130    /// ```
131    #[must_use]
132    pub fn arg(&self) -> Ex {
133        let id = self.inner.write().arena.arg(self.raw_id());
134        self.wrap(id)
135    }
136
137    /// Decompose into `(re, im)` with `self = re + i·im`.
138    ///
139    /// Both parts are real-valued expressions; unknown quantities appear as
140    /// `re(…)`/`im(…)` nodes.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use symplex::prelude::*;
146    ///
147    /// let ctx = Context::new();
148    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
149    /// let z = (&ctx.i_unit() * &x).exp();          // e^{ix}
150    /// let (re, im) = z.as_real_imag();
151    /// assert_eq!(format!("{re}"), "cos(x)");
152    /// assert_eq!(format!("{im}"), "sin(x)");
153    /// ```
154    #[must_use]
155    pub fn as_real_imag(&self) -> (Ex, Ex) {
156        let (re, im) = self.inner.write().arena.as_real_imag_expr(self.raw_id());
157        (self.wrap(re), self.wrap(im))
158    }
159
160    /// Rewrite as `re + i·im`, expanding every unknown symbol `z` into
161    /// `re(z) + i·im(z)` (symbols assumed real are left alone).
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use symplex::prelude::*;
167    ///
168    /// let ctx = Context::new();
169    /// let z = ctx.symbol("z");
170    /// assert_eq!(format!("{}", z.expand_complex()), "im(z)*I + re(z)");
171    ///
172    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
173    /// let e = (&ctx.i_unit() * &x).exp().expand_complex();
174    /// assert_eq!(format!("{e}"), "sin(x)*I + cos(x)");
175    /// ```
176    #[must_use]
177    pub fn expand_complex(&self) -> Ex {
178        let id = {
179            let mut guard = self.inner.write();
180            crate::base::complex::expand_complex(&mut guard.arena, self.raw_id())
181        };
182        self.wrap(id)
183    }
184
185    /// Polar form `(|self|, arg(self))`.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use symplex::prelude::*;
191    ///
192    /// let ctx = Context::new();
193    /// let z = &ctx.int(3) + &(&ctx.int(4) * &ctx.i_unit());
194    /// let (r, theta) = z.polar();
195    /// assert!((r.eval_f64().unwrap() - 5.0).abs() < 1e-12);
196    /// assert!((theta.eval_f64().unwrap() - (4.0f64).atan2(3.0)).abs() < 1e-12);
197    /// ```
198    #[must_use]
199    pub fn polar(&self) -> (Ex, Ex) {
200        (self.abs(), self.arg())
201    }
202
203    /// `|self|² = re² + im² = self·conjugate(self)`.
204    ///
205    /// When the real/imaginary decomposition is fully determined the result
206    /// is `re² + im²`; otherwise it is `self·conjugate(self)`.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use symplex::prelude::*;
212    ///
213    /// let ctx = Context::new();
214    /// let z = &ctx.int(3) + &(&ctx.int(4) * &ctx.i_unit());
215    /// assert_eq!(format!("{}", z.abs_squared().eval()), "25");
216    ///
217    /// let w = ctx.symbol("w");
218    /// assert_eq!(format!("{}", w.abs_squared()), "w*conjugate(w)");
219    /// ```
220    #[must_use]
221    pub fn abs_squared(&self) -> Ex {
222        let id = {
223            let mut guard = self.inner.write();
224            let arena = &mut guard.arena;
225            let parts = crate::base::complex::decompose(arena, self.raw_id());
226            if parts.exact {
227                let two = arena.int(2);
228                let re2 = arena.pow(parts.re, two);
229                let im2 = arena.pow(parts.im, two);
230                arena.add(&[re2, im2])
231            } else {
232                let conj = arena.conjugate(self.raw_id());
233                arena.mul(&[self.raw_id(), conj])
234            }
235        };
236        self.wrap(id)
237    }
238
239    /// Three-valued test: is this expression real-valued?
240    ///
241    /// `Some(true)` if provably real (assumptions, or a decomposition with a
242    /// structurally zero imaginary part), `Some(false)` if provably not real
243    /// (e.g. a non-zero numeric imaginary part), `None` if unknown.
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// use symplex::prelude::*;
249    ///
250    /// let ctx = Context::new();
251    /// let i = ctx.i_unit();
252    /// assert_eq!(ctx.int(3).is_real_valued(), Some(true));
253    /// assert_eq!((&ctx.int(3) + &i).is_real_valued(), Some(false));
254    /// assert_eq!(ctx.symbol("z").is_real_valued(), None);
255    /// assert_eq!(ctx.symbol("z").abs().is_real_valued(), Some(true));
256    /// let x = ctx.symbol_with("x", &[Assumption::Real]);
257    /// assert_eq!((&x + &i).is_real_valued(), Some(false));
258    /// ```
259    #[must_use]
260    pub fn is_real_valued(&self) -> Option<bool> {
261        if let Some(v) = self.query(Props::REAL) {
262            return Some(v);
263        }
264        let mut guard = self.inner.write();
265        let arena = &mut guard.arena;
266        let parts = crate::base::complex::decompose(arena, self.raw_id());
267        if parts.im == arena.zero() {
268            return Some(true);
269        }
270        if parts.exact {
271            // A structurally non-zero *number* as imaginary part → not real.
272            if let Some(r) = arena.as_num(parts.im)
273                && !num_traits::Zero::is_zero(r)
274            {
275                return Some(false);
276            }
277            // Symbolic imaginary part: decide via assumptions on im.
278            let im_id = parts.im;
279            drop(guard);
280            let im_ex = self.wrap(im_id);
281            return match im_ex.query(Props::ZERO) {
282                Some(true) => Some(true),
283                _ => match im_ex.query(Props::NONZERO) {
284                    Some(true) => Some(false),
285                    _ => None,
286                },
287            };
288        }
289        None
290    }
291}
292
293// ═══════════════════════════════════════════════════════════════════════════
294// impl Expr<Numeric> — special functions (0.2 nodes)
295// ═══════════════════════════════════════════════════════════════════════════
296
297impl Expr<Numeric> {
298    /// Sine integral `Si(self) = ∫₀ˣ sin(t)/t dt`.
299    ///
300    /// Exact values: `Si(0) = 0`, `Si(∞) = π/2`, `Si(−x) = −Si(x)`.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use symplex::prelude::*;
306    ///
307    /// let ctx = Context::new();
308    /// let x = ctx.symbol("x");
309    /// assert_eq!(format!("{}", x.si()), "Si(x)");
310    /// assert_eq!(format!("{}", x.si().diff(&x)), "sin(x)/x");
311    /// let v = ctx.int(1).si().eval_f64().unwrap();
312    /// assert!((v - 0.946083070367183).abs() < 1e-14);
313    /// ```
314    #[must_use]
315    pub fn si(&self) -> Ex {
316        let id = self.inner.write().arena.si(self.raw_id());
317        self.wrap(id)
318    }
319
320    /// Cosine integral `Ci(self) = γ + ln(x) + ∫₀ˣ (cos(t) − 1)/t dt`.
321    ///
322    /// Exact values: `Ci(∞) = 0`.  For `x < 0` the numerical value is
323    /// complex: `Ci(−x) = Ci(x) + iπ`.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use symplex::prelude::*;
329    ///
330    /// let ctx = Context::new();
331    /// let v = ctx.int(1).ci().eval_f64().unwrap();
332    /// assert!((v - 0.337403922900968).abs() < 1e-14);
333    /// ```
334    #[must_use]
335    pub fn ci(&self) -> Ex {
336        let id = self.inner.write().arena.ci(self.raw_id());
337        self.wrap(id)
338    }
339
340    /// Exponential integral `Ei(self) = −∫_{−x}^{∞} e^{−t}/t dt` (Cauchy
341    /// principal value for `x > 0`).
342    ///
343    /// Exact values: `Ei(−∞) = 0`, `Ei(∞) = ∞`.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use symplex::prelude::*;
349    ///
350    /// let ctx = Context::new();
351    /// let v = ctx.int(1).ei().eval_f64().unwrap();
352    /// assert!((v - 1.895117816355937).abs() < 1e-14);
353    /// let x = ctx.symbol("x");
354    /// assert_eq!(format!("{}", x.ei().diff(&x)), "exp(x)/x");
355    /// ```
356    #[must_use]
357    pub fn ei(&self) -> Ex {
358        let id = self.inner.write().arena.ei(self.raw_id());
359        self.wrap(id)
360    }
361
362    /// Logarithmic integral `li(self) = ∫₀ˣ dt/ln(t) = Ei(ln x)`.
363    ///
364    /// Exact values: `li(0) = 0`, `li(1) = −∞`, `li(e^y) = Ei(y)`.
365    ///
366    /// # Examples
367    ///
368    /// ```
369    /// use symplex::prelude::*;
370    ///
371    /// let ctx = Context::new();
372    /// let v = ctx.int(2).li().eval_f64().unwrap();
373    /// assert!((v - 1.045163780117493).abs() < 1e-14);
374    /// let x = ctx.symbol("x");
375    /// assert_eq!(format!("{}", x.li().diff(&x)), "1/ln(x)");
376    /// ```
377    #[must_use]
378    pub fn li(&self) -> Ex {
379        let id = self.inner.write().arena.li(self.raw_id());
380        self.wrap(id)
381    }
382
383    /// Riemann zeta function `ζ(self)`.
384    ///
385    /// Exact values: `ζ(1) = zoo`, `ζ(0) = −1/2`, `ζ(−n) = −Bₙ₊₁/(n+1)`,
386    /// `ζ(2k)` as a rational multiple of `π^{2k}`; odd positive arguments
387    /// stay symbolic.
388    ///
389    /// # Examples
390    ///
391    /// ```
392    /// use symplex::prelude::*;
393    ///
394    /// let ctx = Context::new();
395    /// assert_eq!(format!("{}", ctx.int(2).zeta()), "1/6*pi^2");
396    /// assert_eq!(format!("{}", ctx.int(4).zeta()), "1/90*pi^4");
397    /// assert_eq!(format!("{}", ctx.int(-1).zeta()), "-1/12");
398    /// assert_eq!(format!("{}", ctx.int(3).zeta()), "zeta(3)");
399    /// let v = ctx.int(3).zeta().eval_f64().unwrap();
400    /// assert!((v - 1.202056903159594).abs() < 1e-14);
401    /// ```
402    #[must_use]
403    pub fn zeta(&self) -> Ex {
404        let id = self.inner.write().arena.zeta(self.raw_id());
405        self.wrap(id)
406    }
407
408    /// Polygamma function `ψ⁽ⁿ⁾(self)` — the `n`-th derivative of the
409    /// digamma function.
410    ///
411    /// `ψ⁽⁰⁾` is canonicalised to [`digamma`](Self::digamma); `ψ⁽ⁿ⁾(1)`,
412    /// `ψ⁽ⁿ⁾(1/2)` and small integer / half-integer shifts of them fold to
413    /// multiples of `ζ(n+1)`.
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// use symplex::prelude::*;
419    ///
420    /// let ctx = Context::new();
421    /// let one = ctx.int(1);
422    /// // trigamma(1) = ζ(2) = π²/6
423    /// assert_eq!(format!("{}", one.polygamma(&one)), "1/6*pi^2");
424    /// let x = ctx.symbol("x");
425    /// assert_eq!(format!("{}", x.digamma().diff(&x)), "polygamma(1, x)");
426    /// let v = ctx.int(1).polygamma(&one).eval_f64().unwrap();
427    /// assert!((v - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-14);
428    /// ```
429    #[must_use]
430    pub fn polygamma(&self, n: &Ex) -> Ex {
431        let n_id = self.checked_id(n);
432        let id = self.inner.write().arena.polygamma(n_id, self.raw_id());
433        self.wrap(id)
434    }
435
436    /// Kronecker delta `δ(self, other)`: `1` if the arguments are equal,
437    /// `0` if they are provably different numbers, otherwise symbolic.
438    ///
439    /// # Examples
440    ///
441    /// ```
442    /// use symplex::prelude::*;
443    ///
444    /// let ctx = Context::new();
445    /// let i = ctx.symbol("i");
446    /// let j = ctx.symbol("j");
447    /// assert_eq!(format!("{}", i.kronecker_delta(&i)), "1");
448    /// assert_eq!(format!("{}", ctx.int(2).kronecker_delta(&ctx.int(3))), "0");
449    /// let d = i.kronecker_delta(&j);
450    /// assert_eq!(format!("{d}"), "KroneckerDelta(i, j)");
451    /// assert_eq!(d, j.kronecker_delta(&i)); // symmetric
452    /// assert_eq!(format!("{}", (&i + 1).kronecker_delta(&i)), "0");
453    /// ```
454    #[must_use]
455    pub fn kronecker_delta(&self, other: &Ex) -> Ex {
456        let other_id = self.checked_id(other);
457        let id = self
458            .inner
459            .write()
460            .arena
461            .kronecker_delta(self.raw_id(), other_id);
462        self.wrap(id)
463    }
464}