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}