Skip to main content

symplex/output/
tree.rs

1//! Serializable expression tree for interchange.
2//!
3//! [`ExprTree`] is a standalone, self-contained representation of a
4//! symbolic expression that can be serialized to JSON (or any serde
5//! format) and deserialized back. It is the primary machine-readable
6//! output format for symplex.
7//!
8//! # Round-trip
9//!
10//! ```
11//! use symplex::prelude::*;
12//!
13//! let ctx = Context::new();
14//! let x = ctx.symbol("x");
15//! let expr = &x.powi(2) + &x + 1;
16//!
17//! // Serialize to tree
18//! let tree = expr.to_tree();
19//!
20//! // Serialize to JSON
21//! let json = serde_json::to_string(&tree).unwrap();
22//!
23//! // Deserialize back
24//! let tree2: symplex::tree::ExprTree = serde_json::from_str(&json).unwrap();
25//!
26//! // Convert back to Ex in the same context
27//! let expr2 = ctx.from_tree(&tree2);
28//! assert_eq!(format!("{expr}"), format!("{expr2}"));
29//! ```
30
31use num_bigint::BigInt;
32use num_rational::Ratio;
33use serde::{Deserialize, Serialize};
34
35use crate::base::arena::Arena;
36use crate::base::node::{ExprId, ExprNode};
37
38/// A serializable expression tree.
39///
40/// This is a standalone tree (not arena-indexed) that can be serialized
41/// to JSON or any serde-supported format. Use [`Ex::to_tree()`](crate::api::expr::Ex::to_tree) to
42/// convert from an expression handle, and [`Context::from_tree()`](crate::api::context::Context::from_tree) to
43/// convert back.
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45#[serde(tag = "type")]
46pub enum ExprTree {
47    /// A rational number with arbitrary-precision numerator and denominator.
48    Num {
49        /// Numerator as a decimal string.
50        numer: String,
51        /// Denominator as a decimal string.
52        denom: String,
53    },
54    /// A symbolic variable.
55    Symbol {
56        /// The variable name.
57        name: String,
58    },
59    /// The constant π.
60    Pi,
61    /// Euler's number e.
62    E,
63    /// The imaginary unit i.
64    ImaginaryUnit,
65    /// The Euler–Mascheroni constant γ.
66    EulerGamma,
67    /// Catalan's constant G.
68    Catalan,
69    /// The golden ratio φ.
70    GoldenRatio,
71    /// A named physical constant with a known exact value.
72    PhysicalConstant {
73        /// The display name (e.g., "c", "h", "k_B").
74        name: String,
75        /// The exact value of the constant.
76        value: Box<ExprTree>,
77    },
78    /// Positive infinity.
79    Infinity,
80    /// Negative infinity.
81    NegInfinity,
82    /// Complex infinity (undirected).
83    ComplexInfinity,
84    /// Not-a-number.
85    NaN,
86    /// A sum of terms.
87    Add {
88        /// The summands.
89        terms: Vec<ExprTree>,
90    },
91    /// A product of factors.
92    Mul {
93        /// The multiplicands.
94        factors: Vec<ExprTree>,
95    },
96    /// Exponentiation: base^exp.
97    Pow {
98        /// The base expression.
99        base: Box<ExprTree>,
100        /// The exponent expression.
101        exp: Box<ExprTree>,
102    },
103    /// Negation: -inner.
104    Neg {
105        /// The negated expression.
106        inner: Box<ExprTree>,
107    },
108    /// Sine.
109    Sin {
110        /// The function argument.
111        arg: Box<ExprTree>,
112    },
113    /// Cosine.
114    Cos {
115        /// The function argument.
116        arg: Box<ExprTree>,
117    },
118    /// Tangent.
119    Tan {
120        /// The function argument.
121        arg: Box<ExprTree>,
122    },
123    /// Natural exponential.
124    Exp {
125        /// The function argument.
126        arg: Box<ExprTree>,
127    },
128    /// Natural logarithm.
129    Ln {
130        /// The function argument.
131        arg: Box<ExprTree>,
132    },
133    /// Square root.
134    Sqrt {
135        /// The function argument.
136        arg: Box<ExprTree>,
137    },
138    /// Absolute value.
139    Abs {
140        /// The function argument.
141        arg: Box<ExprTree>,
142    },
143    /// Inverse sine.
144    Asin {
145        /// The function argument.
146        arg: Box<ExprTree>,
147    },
148    /// Inverse cosine.
149    Acos {
150        /// The function argument.
151        arg: Box<ExprTree>,
152    },
153    /// Inverse tangent.
154    Atan {
155        /// The function argument.
156        arg: Box<ExprTree>,
157    },
158    /// Two-argument arctangent: atan2(y, x).
159    Atan2 {
160        /// The y coordinate.
161        y: Box<ExprTree>,
162        /// The x coordinate.
163        x: Box<ExprTree>,
164    },
165    /// Hyperbolic sine.
166    Sinh {
167        /// The function argument.
168        arg: Box<ExprTree>,
169    },
170    /// Hyperbolic cosine.
171    Cosh {
172        /// The function argument.
173        arg: Box<ExprTree>,
174    },
175    /// Hyperbolic tangent.
176    Tanh {
177        /// The function argument.
178        arg: Box<ExprTree>,
179    },
180    /// Inverse hyperbolic sine.
181    Asinh {
182        /// The function argument.
183        arg: Box<ExprTree>,
184    },
185    /// Inverse hyperbolic cosine.
186    Acosh {
187        /// The function argument.
188        arg: Box<ExprTree>,
189    },
190    /// Inverse hyperbolic tangent.
191    Atanh {
192        /// The function argument.
193        arg: Box<ExprTree>,
194    },
195    /// Sign function: 1 if positive, -1 if negative, 0 if zero.
196    Sign {
197        /// The function argument.
198        arg: Box<ExprTree>,
199    },
200    /// Heaviside step function: H(x) = 0 for x<0, 1/2 for x=0, 1 for x>0.
201    Heaviside {
202        /// The function argument.
203        arg: Box<ExprTree>,
204    },
205    /// Dirac delta distribution: δ(x) = 0 for x≠0, symbolic at x=0.
206    DiracDelta {
207        /// The function argument.
208        arg: Box<ExprTree>,
209    },
210    /// Gamma function: Γ(x).
211    Gamma {
212        /// The function argument.
213        arg: Box<ExprTree>,
214    },
215    /// Log-gamma function: ln(Γ(x)).
216    LogGamma {
217        /// The function argument.
218        arg: Box<ExprTree>,
219    },
220    /// Digamma function: ψ(x) = Γ'(x)/Γ(x).
221    Digamma {
222        /// The function argument.
223        arg: Box<ExprTree>,
224    },
225    /// Error function: erf(x).
226    Erf {
227        /// The function argument.
228        arg: Box<ExprTree>,
229    },
230    /// Complementary error function: erfc(x) = 1 - erf(x).
231    Erfc {
232        /// The function argument.
233        arg: Box<ExprTree>,
234    },
235    /// Lambert W function (principal branch): W(x)·exp(W(x)) = x.
236    LambertW {
237        /// The function argument.
238        arg: Box<ExprTree>,
239    },
240    /// Beta function: B(a, b) = Γ(a)Γ(b)/Γ(a+b).
241    Beta {
242        /// First parameter.
243        a: Box<ExprTree>,
244        /// Second parameter.
245        b: Box<ExprTree>,
246    },
247    /// Real part: re(z).
248    Re {
249        /// The complex argument.
250        arg: Box<ExprTree>,
251    },
252    /// Imaginary part: im(z).
253    Im {
254        /// The complex argument.
255        arg: Box<ExprTree>,
256    },
257    /// Complex conjugate.
258    Conjugate {
259        /// The complex argument.
260        arg: Box<ExprTree>,
261    },
262    /// Principal complex argument arg(z) ∈ (−π, π].
263    Arg {
264        /// The complex argument.
265        arg: Box<ExprTree>,
266    },
267    /// Sine integral Si(x).
268    Si {
269        /// The function argument.
270        arg: Box<ExprTree>,
271    },
272    /// Cosine integral Ci(x).
273    Ci {
274        /// The function argument.
275        arg: Box<ExprTree>,
276    },
277    /// Exponential integral Ei(x).
278    Ei {
279        /// The function argument.
280        arg: Box<ExprTree>,
281    },
282    /// Logarithmic integral li(x).
283    Li {
284        /// The function argument.
285        arg: Box<ExprTree>,
286    },
287    /// Riemann zeta function ζ(s).
288    Zeta {
289        /// The function argument.
290        arg: Box<ExprTree>,
291    },
292    /// Polygamma function ψ⁽ⁿ⁾(x).
293    Polygamma {
294        /// The derivative order n.
295        n: Box<ExprTree>,
296        /// The function argument.
297        arg: Box<ExprTree>,
298    },
299    /// Kronecker delta δᵢⱼ.
300    KroneckerDelta {
301        /// First index.
302        i: Box<ExprTree>,
303        /// Second index.
304        j: Box<ExprTree>,
305    },
306    /// Floor function: greatest integer <= x.
307    Floor {
308        /// The function argument.
309        arg: Box<ExprTree>,
310    },
311    /// Ceiling function: least integer >= x.
312    Ceiling {
313        /// The function argument.
314        arg: Box<ExprTree>,
315    },
316    /// N-ary minimum.
317    Min {
318        /// The candidate expressions.
319        args: Vec<ExprTree>,
320    },
321    /// N-ary maximum.
322    Max {
323        /// The candidate expressions.
324        args: Vec<ExprTree>,
325    },
326    /// Boolean true.
327    BoolTrue,
328    /// Boolean false.
329    BoolFalse,
330    /// Greater than: lhs > rhs.
331    Gt {
332        /// Left operand.
333        lhs: Box<ExprTree>,
334        /// Right operand.
335        rhs: Box<ExprTree>,
336    },
337    /// Greater than or equal: lhs >= rhs.
338    Ge {
339        /// Left operand.
340        lhs: Box<ExprTree>,
341        /// Right operand.
342        rhs: Box<ExprTree>,
343    },
344    /// Mathematical equality test: lhs == rhs.
345    Eq_ {
346        /// Left operand.
347        lhs: Box<ExprTree>,
348        /// Right operand.
349        rhs: Box<ExprTree>,
350    },
351    /// Not equal: lhs != rhs.
352    Ne {
353        /// Left operand.
354        lhs: Box<ExprTree>,
355        /// Right operand.
356        rhs: Box<ExprTree>,
357    },
358    /// Logical conjunction (n-ary).
359    And {
360        /// The conjuncts.
361        args: Vec<ExprTree>,
362    },
363    /// Logical disjunction (n-ary).
364    Or {
365        /// The disjuncts.
366        args: Vec<ExprTree>,
367    },
368    /// Logical negation.
369    Not {
370        /// The negated expression.
371        arg: Box<ExprTree>,
372    },
373    /// Piecewise function: list of (value, condition) pairs.
374    Piecewise {
375        /// List of (value, condition) pairs.
376        pieces: Vec<(ExprTree, ExprTree)>,
377    },
378    /// Application of a named function.
379    Apply {
380        /// The function name.
381        name: String,
382        /// The function arguments.
383        args: Vec<ExprTree>,
384    },
385    /// Formal derivative.
386    Derivative {
387        /// The expression being differentiated.
388        body: Box<ExprTree>,
389        /// The variable of differentiation.
390        var: Box<ExprTree>,
391    },
392    /// Formal integral.
393    Integral {
394        /// The integrand.
395        body: Box<ExprTree>,
396        /// The variable of integration.
397        var: Box<ExprTree>,
398    },
399    /// Formal definite integral: `∫_lower^upper body dvar`.
400    DefiniteIntegral {
401        /// The integrand.
402        body: Box<ExprTree>,
403        /// The variable of integration (bound inside `body`).
404        var: Box<ExprTree>,
405        /// Lower bound of integration.
406        lower: Box<ExprTree>,
407        /// Upper bound of integration.
408        upper: Box<ExprTree>,
409    },
410    /// Symbolic summation: Sum(body, var, lower, upper).
411    Sum {
412        /// The expression being summed.
413        body: Box<ExprTree>,
414        /// The index variable.
415        var: Box<ExprTree>,
416        /// Lower bound of summation.
417        lower: Box<ExprTree>,
418        /// Upper bound of summation.
419        upper: Box<ExprTree>,
420    },
421    /// Symbolic product: Product(body, var, lower, upper).
422    Product_ {
423        /// The expression being multiplied.
424        body: Box<ExprTree>,
425        /// The index variable.
426        var: Box<ExprTree>,
427        /// Lower bound of the product.
428        lower: Box<ExprTree>,
429        /// Upper bound of the product.
430        upper: Box<ExprTree>,
431    },
432    /// The empty set ∅.
433    EmptySet,
434    /// The universal set.
435    UniversalSet,
436    /// A closed/open interval with flags encoding open/closed.
437    /// Bits: 0x01 = left_open, 0x02 = right_open.
438    Interval {
439        /// Left endpoint.
440        start: Box<ExprTree>,
441        /// Right endpoint.
442        end: Box<ExprTree>,
443        /// Bitfield: 0x01 = left open, 0x02 = right open.
444        flags: u8,
445    },
446    /// A finite set of elements {a, b, c, ...}.
447    FiniteSet {
448        /// The set elements.
449        elements: Vec<ExprTree>,
450    },
451    /// Union of sets: A ∪ B ∪ C ∪ ...
452    SetUnion {
453        /// The sets being united.
454        sets: Vec<ExprTree>,
455    },
456    /// Intersection of sets: A ∩ B ∩ C ∩ ...
457    SetIntersection {
458        /// The sets being intersected.
459        sets: Vec<ExprTree>,
460    },
461    /// Set complement (relative): A \ B.
462    SetComplement {
463        /// The set to complement.
464        set: Box<ExprTree>,
465        /// The universe set to complement within.
466        universe: Box<ExprTree>,
467    },
468    /// Limit: lim_{var -> point} body.
469    Limit {
470        /// The expression to take the limit of.
471        body: Box<ExprTree>,
472        /// The variable approaching the limit point.
473        var: Box<ExprTree>,
474        /// The point being approached.
475        point: Box<ExprTree>,
476    },
477    /// Series expansion of body around point in var up to order.
478    Series {
479        /// The expression to expand.
480        body: Box<ExprTree>,
481        /// The expansion variable.
482        var: Box<ExprTree>,
483        /// The expansion point.
484        point: Box<ExprTree>,
485        /// The truncation order.
486        order: Box<ExprTree>,
487    },
488    /// Laplace transform: L{body}(t -> s).
489    LaplaceTransform {
490        /// The time-domain expression.
491        body: Box<ExprTree>,
492        /// The time variable.
493        t: Box<ExprTree>,
494        /// The frequency variable.
495        s: Box<ExprTree>,
496    },
497    /// Inverse Laplace transform: L^{-1}{body}(s -> t).
498    InverseLaplaceTransform {
499        /// The frequency-domain expression.
500        body: Box<ExprTree>,
501        /// The frequency variable.
502        s: Box<ExprTree>,
503        /// The time variable.
504        t: Box<ExprTree>,
505    },
506    /// Residue of body at var = point.
507    Residue {
508        /// The expression to compute the residue of.
509        body: Box<ExprTree>,
510        /// The variable.
511        var: Box<ExprTree>,
512        /// The pole location.
513        point: Box<ExprTree>,
514    },
515    /// Root of a polynomial: RootOf(poly, index).
516    RootOf {
517        /// The polynomial expression.
518        poly: Box<ExprTree>,
519        /// The root index (0-based).
520        index: Box<ExprTree>,
521    },
522    /// Differential equation solver: DSolve(expr, func, var).
523    DSolve {
524        /// The ODE expression (equal to zero).
525        expr: Box<ExprTree>,
526        /// The unknown function.
527        func: Box<ExprTree>,
528        /// The independent variable.
529        var: Box<ExprTree>,
530    },
531    /// Sum over roots of a polynomial: RootSum(poly, body, sumvar).
532    RootSum {
533        /// The polynomial whose roots are summed over.
534        poly: Box<ExprTree>,
535        /// The body expression evaluated at each root.
536        body: Box<ExprTree>,
537        /// The bound summation variable.
538        sumvar: Box<ExprTree>,
539    },
540    /// Condition set: {var | condition}.
541    ConditionSet {
542        /// The set variable.
543        var: Box<ExprTree>,
544        /// The membership condition.
545        condition: Box<ExprTree>,
546    },
547}
548
549// ═══════════════════════════════════════════════════════════════════════════
550// ExprId → ExprTree (serialization direction)
551// ═══════════════════════════════════════════════════════════════════════════
552
553/// Convert an arena expression to a standalone [`ExprTree`].
554///
555/// Uses exhaustive matching on [`ExprNode`] — adding a new variant
556/// without handling it here is a compile error.
557pub(crate) fn expr_to_tree(arena: &Arena, id: ExprId) -> ExprTree {
558    match arena.node(id).clone() {
559        ExprNode::Num(nid) => {
560            let r = arena.num(nid);
561            ExprTree::Num {
562                numer: r.numer().to_string(),
563                denom: r.denom().to_string(),
564            }
565        }
566        ExprNode::Symbol(sid) => ExprTree::Symbol {
567            name: arena.symbol_name(sid).to_owned(),
568        },
569        ExprNode::Pi => ExprTree::Pi,
570        ExprNode::E => ExprTree::E,
571        ExprNode::ImaginaryUnit => ExprTree::ImaginaryUnit,
572        ExprNode::EulerGamma => ExprTree::EulerGamma,
573        ExprNode::Catalan => ExprTree::Catalan,
574        ExprNode::GoldenRatio => ExprTree::GoldenRatio,
575        ExprNode::PhysicalConstant(name_id, value_id) => ExprTree::PhysicalConstant {
576            name: arena.symbol_name(name_id).to_owned(),
577            value: Box::new(expr_to_tree(arena, value_id)),
578        },
579        ExprNode::Infinity => ExprTree::Infinity,
580        ExprNode::NegInfinity => ExprTree::NegInfinity,
581        ExprNode::ComplexInfinity => ExprTree::ComplexInfinity,
582        ExprNode::NaN => ExprTree::NaN,
583        ExprNode::Add(children) => ExprTree::Add {
584            terms: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
585        },
586        ExprNode::Mul(children) => ExprTree::Mul {
587            factors: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
588        },
589        ExprNode::Pow(base, exp) => ExprTree::Pow {
590            base: Box::new(expr_to_tree(arena, base)),
591            exp: Box::new(expr_to_tree(arena, exp)),
592        },
593        ExprNode::Neg(inner) => ExprTree::Neg {
594            inner: Box::new(expr_to_tree(arena, inner)),
595        },
596        ExprNode::Sin(x) => ExprTree::Sin {
597            arg: Box::new(expr_to_tree(arena, x)),
598        },
599        ExprNode::Cos(x) => ExprTree::Cos {
600            arg: Box::new(expr_to_tree(arena, x)),
601        },
602        ExprNode::Tan(x) => ExprTree::Tan {
603            arg: Box::new(expr_to_tree(arena, x)),
604        },
605        ExprNode::Exp(x) => ExprTree::Exp {
606            arg: Box::new(expr_to_tree(arena, x)),
607        },
608        ExprNode::Ln(x) => ExprTree::Ln {
609            arg: Box::new(expr_to_tree(arena, x)),
610        },
611        ExprNode::Abs(x) => ExprTree::Abs {
612            arg: Box::new(expr_to_tree(arena, x)),
613        },
614        ExprNode::Asin(x) => ExprTree::Asin {
615            arg: Box::new(expr_to_tree(arena, x)),
616        },
617        ExprNode::Acos(x) => ExprTree::Acos {
618            arg: Box::new(expr_to_tree(arena, x)),
619        },
620        ExprNode::Atan(x) => ExprTree::Atan {
621            arg: Box::new(expr_to_tree(arena, x)),
622        },
623        ExprNode::Atan2(y, x) => ExprTree::Atan2 {
624            y: Box::new(expr_to_tree(arena, y)),
625            x: Box::new(expr_to_tree(arena, x)),
626        },
627        ExprNode::Sinh(x) => ExprTree::Sinh {
628            arg: Box::new(expr_to_tree(arena, x)),
629        },
630        ExprNode::Cosh(x) => ExprTree::Cosh {
631            arg: Box::new(expr_to_tree(arena, x)),
632        },
633        ExprNode::Tanh(x) => ExprTree::Tanh {
634            arg: Box::new(expr_to_tree(arena, x)),
635        },
636        ExprNode::Asinh(x) => ExprTree::Asinh {
637            arg: Box::new(expr_to_tree(arena, x)),
638        },
639        ExprNode::Acosh(x) => ExprTree::Acosh {
640            arg: Box::new(expr_to_tree(arena, x)),
641        },
642        ExprNode::Atanh(x) => ExprTree::Atanh {
643            arg: Box::new(expr_to_tree(arena, x)),
644        },
645        ExprNode::Sign(x) => ExprTree::Sign {
646            arg: Box::new(expr_to_tree(arena, x)),
647        },
648        ExprNode::Heaviside(x) => ExprTree::Heaviside {
649            arg: Box::new(expr_to_tree(arena, x)),
650        },
651        ExprNode::DiracDelta(x) => ExprTree::DiracDelta {
652            arg: Box::new(expr_to_tree(arena, x)),
653        },
654        ExprNode::Gamma(x) => ExprTree::Gamma {
655            arg: Box::new(expr_to_tree(arena, x)),
656        },
657        ExprNode::LogGamma(x) => ExprTree::LogGamma {
658            arg: Box::new(expr_to_tree(arena, x)),
659        },
660        ExprNode::Digamma(x) => ExprTree::Digamma {
661            arg: Box::new(expr_to_tree(arena, x)),
662        },
663        ExprNode::Erf(x) => ExprTree::Erf {
664            arg: Box::new(expr_to_tree(arena, x)),
665        },
666        ExprNode::Erfc(x) => ExprTree::Erfc {
667            arg: Box::new(expr_to_tree(arena, x)),
668        },
669        ExprNode::LambertW(x) => ExprTree::LambertW {
670            arg: Box::new(expr_to_tree(arena, x)),
671        },
672        ExprNode::Beta(a, b) => ExprTree::Beta {
673            a: Box::new(expr_to_tree(arena, a)),
674            b: Box::new(expr_to_tree(arena, b)),
675        },
676        ExprNode::Re(x) => ExprTree::Re {
677            arg: Box::new(expr_to_tree(arena, x)),
678        },
679        ExprNode::Im(x) => ExprTree::Im {
680            arg: Box::new(expr_to_tree(arena, x)),
681        },
682        ExprNode::Conjugate(x) => ExprTree::Conjugate {
683            arg: Box::new(expr_to_tree(arena, x)),
684        },
685        ExprNode::Arg(x) => ExprTree::Arg {
686            arg: Box::new(expr_to_tree(arena, x)),
687        },
688        ExprNode::Si(x) => ExprTree::Si {
689            arg: Box::new(expr_to_tree(arena, x)),
690        },
691        ExprNode::Ci(x) => ExprTree::Ci {
692            arg: Box::new(expr_to_tree(arena, x)),
693        },
694        ExprNode::Ei(x) => ExprTree::Ei {
695            arg: Box::new(expr_to_tree(arena, x)),
696        },
697        ExprNode::Li(x) => ExprTree::Li {
698            arg: Box::new(expr_to_tree(arena, x)),
699        },
700        ExprNode::Zeta(x) => ExprTree::Zeta {
701            arg: Box::new(expr_to_tree(arena, x)),
702        },
703        ExprNode::Polygamma(n, x) => ExprTree::Polygamma {
704            n: Box::new(expr_to_tree(arena, n)),
705            arg: Box::new(expr_to_tree(arena, x)),
706        },
707        ExprNode::KroneckerDelta(i, j) => ExprTree::KroneckerDelta {
708            i: Box::new(expr_to_tree(arena, i)),
709            j: Box::new(expr_to_tree(arena, j)),
710        },
711        ExprNode::Floor(x) => ExprTree::Floor {
712            arg: Box::new(expr_to_tree(arena, x)),
713        },
714        ExprNode::Ceiling(x) => ExprTree::Ceiling {
715            arg: Box::new(expr_to_tree(arena, x)),
716        },
717        ExprNode::Min(children) => ExprTree::Min {
718            args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
719        },
720        ExprNode::Max(children) => ExprTree::Max {
721            args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
722        },
723        ExprNode::Apply(sid, args) => ExprTree::Apply {
724            name: arena.symbol_name(sid).to_owned(),
725            args: args.iter().map(|&a| expr_to_tree(arena, a)).collect(),
726        },
727        ExprNode::Derivative(body, var) => ExprTree::Derivative {
728            body: Box::new(expr_to_tree(arena, body)),
729            var: Box::new(expr_to_tree(arena, var)),
730        },
731        ExprNode::Integral(body, var) => ExprTree::Integral {
732            body: Box::new(expr_to_tree(arena, body)),
733            var: Box::new(expr_to_tree(arena, var)),
734        },
735        ExprNode::DefiniteIntegral(body, var, lo, hi) => ExprTree::DefiniteIntegral {
736            body: Box::new(expr_to_tree(arena, body)),
737            var: Box::new(expr_to_tree(arena, var)),
738            lower: Box::new(expr_to_tree(arena, lo)),
739            upper: Box::new(expr_to_tree(arena, hi)),
740        },
741        ExprNode::Sum(body, var, lo, hi) => ExprTree::Sum {
742            body: Box::new(expr_to_tree(arena, body)),
743            var: Box::new(expr_to_tree(arena, var)),
744            lower: Box::new(expr_to_tree(arena, lo)),
745            upper: Box::new(expr_to_tree(arena, hi)),
746        },
747        ExprNode::Product_(body, var, lo, hi) => ExprTree::Product_ {
748            body: Box::new(expr_to_tree(arena, body)),
749            var: Box::new(expr_to_tree(arena, var)),
750            lower: Box::new(expr_to_tree(arena, lo)),
751            upper: Box::new(expr_to_tree(arena, hi)),
752        },
753        ExprNode::Factorial(x) => ExprTree::Apply {
754            name: "factorial".to_owned(),
755            args: vec![expr_to_tree(arena, x)],
756        },
757        ExprNode::Binomial(n, k) => ExprTree::Apply {
758            name: "binomial".to_owned(),
759            args: vec![expr_to_tree(arena, n), expr_to_tree(arena, k)],
760        },
761        ExprNode::BoolTrue => ExprTree::BoolTrue,
762        ExprNode::BoolFalse => ExprTree::BoolFalse,
763        ExprNode::Gt(a, b) => ExprTree::Gt {
764            lhs: Box::new(expr_to_tree(arena, a)),
765            rhs: Box::new(expr_to_tree(arena, b)),
766        },
767        ExprNode::Ge(a, b) => ExprTree::Ge {
768            lhs: Box::new(expr_to_tree(arena, a)),
769            rhs: Box::new(expr_to_tree(arena, b)),
770        },
771        ExprNode::Eq_(a, b) => ExprTree::Eq_ {
772            lhs: Box::new(expr_to_tree(arena, a)),
773            rhs: Box::new(expr_to_tree(arena, b)),
774        },
775        ExprNode::Ne(a, b) => ExprTree::Ne {
776            lhs: Box::new(expr_to_tree(arena, a)),
777            rhs: Box::new(expr_to_tree(arena, b)),
778        },
779        ExprNode::And(children) => ExprTree::And {
780            args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
781        },
782        ExprNode::Or(children) => ExprTree::Or {
783            args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
784        },
785        ExprNode::Not(x) => ExprTree::Not {
786            arg: Box::new(expr_to_tree(arena, x)),
787        },
788        ExprNode::Piecewise(children) => ExprTree::Piecewise {
789            pieces: children
790                .iter()
791                .map(|&(val, cond)| (expr_to_tree(arena, val), expr_to_tree(arena, cond)))
792                .collect(),
793        },
794        ExprNode::EmptySet => ExprTree::EmptySet,
795        ExprNode::UniversalSet => ExprTree::UniversalSet,
796        ExprNode::Interval(start, end, flags) => ExprTree::Interval {
797            start: Box::new(expr_to_tree(arena, start)),
798            end: Box::new(expr_to_tree(arena, end)),
799            flags,
800        },
801        ExprNode::FiniteSet(elems) => ExprTree::FiniteSet {
802            elements: elems.iter().map(|&e| expr_to_tree(arena, e)).collect(),
803        },
804        ExprNode::SetUnion(sets) => ExprTree::SetUnion {
805            sets: sets.iter().map(|&s| expr_to_tree(arena, s)).collect(),
806        },
807        ExprNode::SetIntersection(sets) => ExprTree::SetIntersection {
808            sets: sets.iter().map(|&s| expr_to_tree(arena, s)).collect(),
809        },
810        ExprNode::SetComplement(a, b) => ExprTree::SetComplement {
811            set: Box::new(expr_to_tree(arena, a)),
812            universe: Box::new(expr_to_tree(arena, b)),
813        },
814        ExprNode::Limit(body, var, point) => ExprTree::Limit {
815            body: Box::new(expr_to_tree(arena, body)),
816            var: Box::new(expr_to_tree(arena, var)),
817            point: Box::new(expr_to_tree(arena, point)),
818        },
819        ExprNode::Series(body, var, point, order) => ExprTree::Series {
820            body: Box::new(expr_to_tree(arena, body)),
821            var: Box::new(expr_to_tree(arena, var)),
822            point: Box::new(expr_to_tree(arena, point)),
823            order: Box::new(expr_to_tree(arena, order)),
824        },
825        ExprNode::LaplaceTransform(body, t, s) => ExprTree::LaplaceTransform {
826            body: Box::new(expr_to_tree(arena, body)),
827            t: Box::new(expr_to_tree(arena, t)),
828            s: Box::new(expr_to_tree(arena, s)),
829        },
830        ExprNode::InverseLaplaceTransform(body, s, t) => ExprTree::InverseLaplaceTransform {
831            body: Box::new(expr_to_tree(arena, body)),
832            s: Box::new(expr_to_tree(arena, s)),
833            t: Box::new(expr_to_tree(arena, t)),
834        },
835        ExprNode::Residue(body, var, point) => ExprTree::Residue {
836            body: Box::new(expr_to_tree(arena, body)),
837            var: Box::new(expr_to_tree(arena, var)),
838            point: Box::new(expr_to_tree(arena, point)),
839        },
840        ExprNode::RootOf(poly, index) => ExprTree::RootOf {
841            poly: Box::new(expr_to_tree(arena, poly)),
842            index: Box::new(expr_to_tree(arena, index)),
843        },
844        ExprNode::DSolve(expr, func, var) => ExprTree::DSolve {
845            expr: Box::new(expr_to_tree(arena, expr)),
846            func: Box::new(expr_to_tree(arena, func)),
847            var: Box::new(expr_to_tree(arena, var)),
848        },
849        ExprNode::RootSum(poly, body, sumvar) => ExprTree::RootSum {
850            poly: Box::new(expr_to_tree(arena, poly)),
851            body: Box::new(expr_to_tree(arena, body)),
852            sumvar: Box::new(expr_to_tree(arena, sumvar)),
853        },
854        ExprNode::ConditionSet(var, condition) => ExprTree::ConditionSet {
855            var: Box::new(expr_to_tree(arena, var)),
856            condition: Box::new(expr_to_tree(arena, condition)),
857        },
858    }
859}
860
861// ═══════════════════════════════════════════════════════════════════════════
862// ExprTree → ExprId (deserialization direction)
863// ═══════════════════════════════════════════════════════════════════════════
864
865/// Convert a standalone [`ExprTree`] back into an arena expression.
866///
867/// The resulting expression is fully canonicalized (it goes through
868/// the arena's canonical constructors).
869pub(crate) fn tree_to_expr(arena: &mut Arena, tree: &ExprTree) -> ExprId {
870    match tree {
871        ExprTree::Num { numer, denom } => {
872            let n: BigInt = numer.parse().unwrap_or_default();
873            let d: BigInt = denom.parse().unwrap_or_else(|_| BigInt::from(1));
874            let r = Ratio::new(n, d);
875            let nid = arena.intern_num(r);
876            arena.intern(ExprNode::Num(nid))
877        }
878        ExprTree::Symbol { name } => arena.symbol(name),
879        ExprTree::Pi => arena.pi,
880        ExprTree::E => arena.e_const,
881        ExprTree::ImaginaryUnit => arena.i_unit,
882        ExprTree::EulerGamma => arena.euler_gamma,
883        ExprTree::Catalan => arena.catalan,
884        ExprTree::GoldenRatio => arena.golden_ratio,
885        ExprTree::PhysicalConstant { name, value } => {
886            let val_id = tree_to_expr(arena, value);
887            arena.physical_constant(name, val_id)
888        }
889        ExprTree::Infinity => arena.infinity,
890        ExprTree::NegInfinity => arena.neg_infinity,
891        ExprTree::ComplexInfinity => arena.complex_infinity,
892        ExprTree::NaN => arena.nan,
893        ExprTree::Add { terms } => {
894            let ids: Vec<ExprId> = terms.iter().map(|t| tree_to_expr(arena, t)).collect();
895            arena.add(&ids)
896        }
897        ExprTree::Mul { factors } => {
898            let ids: Vec<ExprId> = factors.iter().map(|f| tree_to_expr(arena, f)).collect();
899            arena.mul(&ids)
900        }
901        ExprTree::Pow { base, exp } => {
902            let b = tree_to_expr(arena, base);
903            let e = tree_to_expr(arena, exp);
904            arena.pow(b, e)
905        }
906        ExprTree::Neg { inner } => {
907            let x = tree_to_expr(arena, inner);
908            arena.neg(x)
909        }
910        ExprTree::Sin { arg } => {
911            let x = tree_to_expr(arena, arg);
912            arena.sin(x)
913        }
914        ExprTree::Cos { arg } => {
915            let x = tree_to_expr(arena, arg);
916            arena.cos(x)
917        }
918        ExprTree::Tan { arg } => {
919            let x = tree_to_expr(arena, arg);
920            arena.tan(x)
921        }
922        ExprTree::Exp { arg } => {
923            let x = tree_to_expr(arena, arg);
924            arena.exp(x)
925        }
926        ExprTree::Ln { arg } => {
927            let x = tree_to_expr(arena, arg);
928            arena.ln(x)
929        }
930        ExprTree::Sqrt { arg } => {
931            // Legacy compatibility: convert to Pow(arg, 1/2)
932            let x = tree_to_expr(arena, arg);
933            arena.sqrt(x) // which now produces Pow(x, 1/2)
934        }
935        ExprTree::Abs { arg } => {
936            let x = tree_to_expr(arena, arg);
937            arena.abs(x)
938        }
939        ExprTree::Asin { arg } => {
940            let x = tree_to_expr(arena, arg);
941            arena.asin(x)
942        }
943        ExprTree::Acos { arg } => {
944            let x = tree_to_expr(arena, arg);
945            arena.acos(x)
946        }
947        ExprTree::Atan { arg } => {
948            let x = tree_to_expr(arena, arg);
949            arena.atan(x)
950        }
951        ExprTree::Atan2 { y, x } => {
952            let yid = tree_to_expr(arena, y);
953            let xid = tree_to_expr(arena, x);
954            arena.atan2(yid, xid)
955        }
956        ExprTree::Sinh { arg } => {
957            let x = tree_to_expr(arena, arg);
958            arena.sinh(x)
959        }
960        ExprTree::Cosh { arg } => {
961            let x = tree_to_expr(arena, arg);
962            arena.cosh(x)
963        }
964        ExprTree::Tanh { arg } => {
965            let x = tree_to_expr(arena, arg);
966            arena.tanh(x)
967        }
968        ExprTree::Asinh { arg } => {
969            let x = tree_to_expr(arena, arg);
970            arena.asinh(x)
971        }
972        ExprTree::Acosh { arg } => {
973            let x = tree_to_expr(arena, arg);
974            arena.acosh(x)
975        }
976        ExprTree::Atanh { arg } => {
977            let x = tree_to_expr(arena, arg);
978            arena.atanh(x)
979        }
980        ExprTree::Sign { arg } => {
981            let x = tree_to_expr(arena, arg);
982            arena.sign(x)
983        }
984        ExprTree::Heaviside { arg } => {
985            let x = tree_to_expr(arena, arg);
986            arena.heaviside(x)
987        }
988        ExprTree::DiracDelta { arg } => {
989            let x = tree_to_expr(arena, arg);
990            arena.dirac_delta(x)
991        }
992        ExprTree::Gamma { arg } => {
993            let x = tree_to_expr(arena, arg);
994            arena.gamma(x)
995        }
996        ExprTree::LogGamma { arg } => {
997            let x = tree_to_expr(arena, arg);
998            arena.log_gamma(x)
999        }
1000        ExprTree::Digamma { arg } => {
1001            let x = tree_to_expr(arena, arg);
1002            arena.digamma(x)
1003        }
1004        ExprTree::Erf { arg } => {
1005            let x = tree_to_expr(arena, arg);
1006            arena.erf(x)
1007        }
1008        ExprTree::Erfc { arg } => {
1009            let x = tree_to_expr(arena, arg);
1010            arena.erfc(x)
1011        }
1012        ExprTree::LambertW { arg } => {
1013            let x = tree_to_expr(arena, arg);
1014            arena.lambertw(x)
1015        }
1016        ExprTree::Beta { a, b } => {
1017            let aid = tree_to_expr(arena, a);
1018            let bid = tree_to_expr(arena, b);
1019            arena.beta(aid, bid)
1020        }
1021        ExprTree::Re { arg } => {
1022            let x = tree_to_expr(arena, arg);
1023            arena.re(x)
1024        }
1025        ExprTree::Im { arg } => {
1026            let x = tree_to_expr(arena, arg);
1027            arena.im(x)
1028        }
1029        ExprTree::Conjugate { arg } => {
1030            let x = tree_to_expr(arena, arg);
1031            arena.conjugate(x)
1032        }
1033        ExprTree::Arg { arg } => {
1034            let x = tree_to_expr(arena, arg);
1035            arena.arg(x)
1036        }
1037        ExprTree::Si { arg } => {
1038            let x = tree_to_expr(arena, arg);
1039            arena.si(x)
1040        }
1041        ExprTree::Ci { arg } => {
1042            let x = tree_to_expr(arena, arg);
1043            arena.ci(x)
1044        }
1045        ExprTree::Ei { arg } => {
1046            let x = tree_to_expr(arena, arg);
1047            arena.ei(x)
1048        }
1049        ExprTree::Li { arg } => {
1050            let x = tree_to_expr(arena, arg);
1051            arena.li(x)
1052        }
1053        ExprTree::Zeta { arg } => {
1054            let x = tree_to_expr(arena, arg);
1055            arena.zeta(x)
1056        }
1057        ExprTree::Polygamma { n, arg } => {
1058            let nid = tree_to_expr(arena, n);
1059            let x = tree_to_expr(arena, arg);
1060            arena.polygamma(nid, x)
1061        }
1062        ExprTree::KroneckerDelta { i, j } => {
1063            let iid = tree_to_expr(arena, i);
1064            let jid = tree_to_expr(arena, j);
1065            arena.kronecker_delta(iid, jid)
1066        }
1067        ExprTree::Floor { arg } => {
1068            let x = tree_to_expr(arena, arg);
1069            arena.floor(x)
1070        }
1071        ExprTree::Ceiling { arg } => {
1072            let x = tree_to_expr(arena, arg);
1073            arena.ceiling(x)
1074        }
1075        ExprTree::Min { args } => {
1076            let ids: smallvec::SmallVec<[ExprId; 4]> =
1077                args.iter().map(|a| tree_to_expr(arena, a)).collect();
1078            arena.intern(ExprNode::Min(ids))
1079        }
1080        ExprTree::Max { args } => {
1081            let ids: smallvec::SmallVec<[ExprId; 4]> =
1082                args.iter().map(|a| tree_to_expr(arena, a)).collect();
1083            arena.intern(ExprNode::Max(ids))
1084        }
1085        ExprTree::Apply { name, args } => {
1086            let sym_id = arena.symbols.intern(name);
1087            let arg_ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
1088            let sv: smallvec::SmallVec<[ExprId; 2]> = arg_ids.into_iter().collect();
1089            arena.intern(ExprNode::Apply(sym_id, sv))
1090        }
1091        ExprTree::Derivative { body, var } => {
1092            let b = tree_to_expr(arena, body);
1093            let v = tree_to_expr(arena, var);
1094            arena.intern(ExprNode::Derivative(b, v))
1095        }
1096        ExprTree::Integral { body, var } => {
1097            let b = tree_to_expr(arena, body);
1098            let v = tree_to_expr(arena, var);
1099            arena.intern(ExprNode::Integral(b, v))
1100        }
1101        ExprTree::DefiniteIntegral {
1102            body,
1103            var,
1104            lower,
1105            upper,
1106        } => {
1107            let b = tree_to_expr(arena, body);
1108            let v = tree_to_expr(arena, var);
1109            let lo = tree_to_expr(arena, lower);
1110            let hi = tree_to_expr(arena, upper);
1111            arena.definite_integral(b, v, lo, hi)
1112        }
1113        ExprTree::Sum {
1114            body,
1115            var,
1116            lower,
1117            upper,
1118        } => {
1119            let b = tree_to_expr(arena, body);
1120            let v = tree_to_expr(arena, var);
1121            let lo = tree_to_expr(arena, lower);
1122            let hi = tree_to_expr(arena, upper);
1123            arena.intern(ExprNode::Sum(b, v, lo, hi))
1124        }
1125        ExprTree::Product_ {
1126            body,
1127            var,
1128            lower,
1129            upper,
1130        } => {
1131            let b = tree_to_expr(arena, body);
1132            let v = tree_to_expr(arena, var);
1133            let lo = tree_to_expr(arena, lower);
1134            let hi = tree_to_expr(arena, upper);
1135            arena.intern(ExprNode::Product_(b, v, lo, hi))
1136        }
1137        ExprTree::BoolTrue => arena.bool_true,
1138        ExprTree::BoolFalse => arena.bool_false,
1139        ExprTree::Gt { lhs, rhs } => {
1140            let l = tree_to_expr(arena, lhs);
1141            let r = tree_to_expr(arena, rhs);
1142            arena.gt(l, r)
1143        }
1144        ExprTree::Ge { lhs, rhs } => {
1145            let l = tree_to_expr(arena, lhs);
1146            let r = tree_to_expr(arena, rhs);
1147            arena.ge(l, r)
1148        }
1149        ExprTree::Eq_ { lhs, rhs } => {
1150            let l = tree_to_expr(arena, lhs);
1151            let r = tree_to_expr(arena, rhs);
1152            arena.eq_(l, r)
1153        }
1154        ExprTree::Ne { lhs, rhs } => {
1155            let l = tree_to_expr(arena, lhs);
1156            let r = tree_to_expr(arena, rhs);
1157            arena.ne_(l, r)
1158        }
1159        ExprTree::And { args } => {
1160            let ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
1161            arena.and(&ids)
1162        }
1163        ExprTree::Or { args } => {
1164            let ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
1165            arena.or(&ids)
1166        }
1167        ExprTree::Not { arg } => {
1168            let x = tree_to_expr(arena, arg);
1169            arena.not(x)
1170        }
1171        ExprTree::Piecewise { pieces } => {
1172            let pairs: smallvec::SmallVec<[(ExprId, ExprId); 3]> = pieces
1173                .iter()
1174                .map(|(val, cond)| (tree_to_expr(arena, val), tree_to_expr(arena, cond)))
1175                .collect();
1176            arena.intern(ExprNode::Piecewise(pairs))
1177        }
1178        ExprTree::EmptySet => arena.intern(ExprNode::EmptySet),
1179        ExprTree::UniversalSet => arena.intern(ExprNode::UniversalSet),
1180        ExprTree::Interval { start, end, flags } => {
1181            let s = tree_to_expr(arena, start);
1182            let e = tree_to_expr(arena, end);
1183            arena.intern(ExprNode::Interval(s, e, *flags))
1184        }
1185        ExprTree::FiniteSet { elements } => {
1186            let ids: smallvec::SmallVec<[ExprId; 4]> =
1187                elements.iter().map(|e| tree_to_expr(arena, e)).collect();
1188            arena.intern(ExprNode::FiniteSet(ids))
1189        }
1190        ExprTree::SetUnion { sets } => {
1191            let ids: smallvec::SmallVec<[ExprId; 4]> =
1192                sets.iter().map(|s| tree_to_expr(arena, s)).collect();
1193            arena.intern(ExprNode::SetUnion(ids))
1194        }
1195        ExprTree::SetIntersection { sets } => {
1196            let ids: smallvec::SmallVec<[ExprId; 4]> =
1197                sets.iter().map(|s| tree_to_expr(arena, s)).collect();
1198            arena.intern(ExprNode::SetIntersection(ids))
1199        }
1200        ExprTree::SetComplement { set, universe } => {
1201            let s = tree_to_expr(arena, set);
1202            let u = tree_to_expr(arena, universe);
1203            arena.intern(ExprNode::SetComplement(s, u))
1204        }
1205        ExprTree::Limit { body, var, point } => {
1206            let b = tree_to_expr(arena, body);
1207            let v = tree_to_expr(arena, var);
1208            let p = tree_to_expr(arena, point);
1209            arena.intern(ExprNode::Limit(b, v, p))
1210        }
1211        ExprTree::Series {
1212            body,
1213            var,
1214            point,
1215            order,
1216        } => {
1217            let b = tree_to_expr(arena, body);
1218            let v = tree_to_expr(arena, var);
1219            let p = tree_to_expr(arena, point);
1220            let o = tree_to_expr(arena, order);
1221            arena.intern(ExprNode::Series(b, v, p, o))
1222        }
1223        ExprTree::LaplaceTransform { body, t, s } => {
1224            let b = tree_to_expr(arena, body);
1225            let ti = tree_to_expr(arena, t);
1226            let si = tree_to_expr(arena, s);
1227            arena.intern(ExprNode::LaplaceTransform(b, ti, si))
1228        }
1229        ExprTree::InverseLaplaceTransform { body, s, t } => {
1230            let b = tree_to_expr(arena, body);
1231            let si = tree_to_expr(arena, s);
1232            let ti = tree_to_expr(arena, t);
1233            arena.intern(ExprNode::InverseLaplaceTransform(b, si, ti))
1234        }
1235        ExprTree::Residue { body, var, point } => {
1236            let b = tree_to_expr(arena, body);
1237            let v = tree_to_expr(arena, var);
1238            let p = tree_to_expr(arena, point);
1239            arena.intern(ExprNode::Residue(b, v, p))
1240        }
1241        ExprTree::RootOf { poly, index } => {
1242            let p = tree_to_expr(arena, poly);
1243            let i = tree_to_expr(arena, index);
1244            arena.intern(ExprNode::RootOf(p, i))
1245        }
1246        ExprTree::DSolve { expr, func, var } => {
1247            let e = tree_to_expr(arena, expr);
1248            let f = tree_to_expr(arena, func);
1249            let v = tree_to_expr(arena, var);
1250            arena.intern(ExprNode::DSolve(e, f, v))
1251        }
1252        ExprTree::RootSum { poly, body, sumvar } => {
1253            let p = tree_to_expr(arena, poly);
1254            let b = tree_to_expr(arena, body);
1255            let s = tree_to_expr(arena, sumvar);
1256            arena.intern(ExprNode::RootSum(p, b, s))
1257        }
1258        ExprTree::ConditionSet { var, condition } => {
1259            let v = tree_to_expr(arena, var);
1260            let c = tree_to_expr(arena, condition);
1261            arena.intern(ExprNode::ConditionSet(v, c))
1262        }
1263    }
1264}
1265
1266// ═══════════════════════════════════════════════════════════════════════════
1267// srepr and DOT (0.9.1)
1268// ═══════════════════════════════════════════════════════════════════════════
1269
1270impl ExprTree {
1271    /// The constructor head and the ordered children of this node.
1272    ///
1273    /// `None` children marks an atom (printed as the bare head); `Some`
1274    /// marks a compound node (printed as `head(child, …)`, even with zero
1275    /// children).  Heads follow SymPy's `srepr` where SymPy has the node
1276    /// (`Integer`, `Rational`, `Symbol('x')`, `Add`, `Mul`, `Pow`, `sin`,
1277    /// `log`, `StrictGreaterThan`, `Interval`, …) and the symplex name
1278    /// otherwise (`Neg`, `DefiniteIntegral`, `Series`, `RootSum`).
1279    fn head_and_children(&self) -> (String, Option<Vec<&ExprTree>>) {
1280        type Parts<'t> = (String, Option<Vec<&'t ExprTree>>);
1281        fn one<'t>(head: &str, a: &'t ExprTree) -> Parts<'t> {
1282            (head.to_string(), Some(vec![a]))
1283        }
1284        fn two<'t>(head: &str, a: &'t ExprTree, b: &'t ExprTree) -> Parts<'t> {
1285            (head.to_string(), Some(vec![a, b]))
1286        }
1287        fn many<'t>(head: &str, items: &'t [ExprTree]) -> Parts<'t> {
1288            (head.to_string(), Some(items.iter().collect()))
1289        }
1290        fn atom<'t>(head: &str) -> Parts<'t> {
1291            (head.to_string(), None)
1292        }
1293        match self {
1294            ExprTree::Num { numer, denom } => {
1295                if denom == "1" {
1296                    atom(&format!("Integer({numer})"))
1297                } else {
1298                    atom(&format!("Rational({numer}, {denom})"))
1299                }
1300            }
1301            ExprTree::Symbol { name } => atom(&format!("Symbol('{name}')")),
1302            ExprTree::Pi => atom("pi"),
1303            ExprTree::E => atom("E"),
1304            ExprTree::ImaginaryUnit => atom("I"),
1305            ExprTree::EulerGamma => atom("EulerGamma"),
1306            ExprTree::Catalan => atom("Catalan"),
1307            ExprTree::GoldenRatio => atom("GoldenRatio"),
1308            ExprTree::PhysicalConstant { name, value } => {
1309                one(&format!("PhysicalConstant('{name}')"), value)
1310            }
1311            ExprTree::Infinity => atom("oo"),
1312            ExprTree::NegInfinity => atom("-oo"),
1313            ExprTree::ComplexInfinity => atom("zoo"),
1314            ExprTree::NaN => atom("nan"),
1315            ExprTree::Add { terms } => many("Add", terms),
1316            ExprTree::Mul { factors } => many("Mul", factors),
1317            ExprTree::Pow { base, exp } => two("Pow", base, exp),
1318            ExprTree::Neg { inner } => one("Neg", inner),
1319            ExprTree::Sin { arg } => one("sin", arg),
1320            ExprTree::Cos { arg } => one("cos", arg),
1321            ExprTree::Tan { arg } => one("tan", arg),
1322            ExprTree::Exp { arg } => one("exp", arg),
1323            ExprTree::Ln { arg } => one("log", arg),
1324            ExprTree::Sqrt { arg } => one("sqrt", arg),
1325            ExprTree::Abs { arg } => one("Abs", arg),
1326            ExprTree::Asin { arg } => one("asin", arg),
1327            ExprTree::Acos { arg } => one("acos", arg),
1328            ExprTree::Atan { arg } => one("atan", arg),
1329            ExprTree::Atan2 { y, x } => two("atan2", y, x),
1330            ExprTree::Sinh { arg } => one("sinh", arg),
1331            ExprTree::Cosh { arg } => one("cosh", arg),
1332            ExprTree::Tanh { arg } => one("tanh", arg),
1333            ExprTree::Asinh { arg } => one("asinh", arg),
1334            ExprTree::Acosh { arg } => one("acosh", arg),
1335            ExprTree::Atanh { arg } => one("atanh", arg),
1336            ExprTree::Sign { arg } => one("sign", arg),
1337            ExprTree::Heaviside { arg } => one("Heaviside", arg),
1338            ExprTree::DiracDelta { arg } => one("DiracDelta", arg),
1339            ExprTree::Gamma { arg } => one("gamma", arg),
1340            ExprTree::LogGamma { arg } => one("loggamma", arg),
1341            ExprTree::Digamma { arg } => one("digamma", arg),
1342            ExprTree::Erf { arg } => one("erf", arg),
1343            ExprTree::Erfc { arg } => one("erfc", arg),
1344            ExprTree::LambertW { arg } => one("LambertW", arg),
1345            ExprTree::Beta { a, b } => two("beta", a, b),
1346            ExprTree::Re { arg } => one("re", arg),
1347            ExprTree::Im { arg } => one("im", arg),
1348            ExprTree::Conjugate { arg } => one("conjugate", arg),
1349            ExprTree::Arg { arg } => one("arg", arg),
1350            ExprTree::Si { arg } => one("Si", arg),
1351            ExprTree::Ci { arg } => one("Ci", arg),
1352            ExprTree::Ei { arg } => one("Ei", arg),
1353            ExprTree::Li { arg } => one("li", arg),
1354            ExprTree::Zeta { arg } => one("zeta", arg),
1355            ExprTree::Polygamma { n, arg } => two("polygamma", n, arg),
1356            ExprTree::KroneckerDelta { i, j } => two("KroneckerDelta", i, j),
1357            ExprTree::Floor { arg } => one("floor", arg),
1358            ExprTree::Ceiling { arg } => one("ceiling", arg),
1359            ExprTree::Min { args } => many("Min", args),
1360            ExprTree::Max { args } => many("Max", args),
1361            ExprTree::BoolTrue => atom("true"),
1362            ExprTree::BoolFalse => atom("false"),
1363            ExprTree::Gt { lhs, rhs } => two("StrictGreaterThan", lhs, rhs),
1364            ExprTree::Ge { lhs, rhs } => two("GreaterThan", lhs, rhs),
1365            ExprTree::Eq_ { lhs, rhs } => two("Equality", lhs, rhs),
1366            ExprTree::Ne { lhs, rhs } => two("Unequality", lhs, rhs),
1367            ExprTree::And { args } => many("And", args),
1368            ExprTree::Or { args } => many("Or", args),
1369            ExprTree::Not { arg } => one("Not", arg),
1370            ExprTree::Piecewise { pieces } => (
1371                "Piecewise".to_string(),
1372                Some(pieces.iter().flat_map(|(v, c)| [v, c]).collect()),
1373            ),
1374            ExprTree::Apply { name, args } => many(name, args),
1375            ExprTree::Derivative { body, var } => two("Derivative", body, var),
1376            ExprTree::Integral { body, var } => two("Integral", body, var),
1377            ExprTree::DefiniteIntegral {
1378                body,
1379                var,
1380                lower,
1381                upper,
1382            } => (
1383                "DefiniteIntegral".to_string(),
1384                Some(vec![body, var, lower, upper]),
1385            ),
1386            ExprTree::Sum {
1387                body,
1388                var,
1389                lower,
1390                upper,
1391            } => ("Sum".to_string(), Some(vec![body, var, lower, upper])),
1392            ExprTree::Product_ {
1393                body,
1394                var,
1395                lower,
1396                upper,
1397            } => ("Product".to_string(), Some(vec![body, var, lower, upper])),
1398            ExprTree::EmptySet => atom("EmptySet"),
1399            ExprTree::UniversalSet => atom("UniversalSet"),
1400            ExprTree::Interval { start, end, .. } => two("Interval", start, end),
1401            ExprTree::FiniteSet { elements } => many("FiniteSet", elements),
1402            ExprTree::SetUnion { sets } => many("Union", sets),
1403            ExprTree::SetIntersection { sets } => many("Intersection", sets),
1404            ExprTree::SetComplement { set, universe } => two("Complement", set, universe),
1405            ExprTree::Limit { body, var, point } => {
1406                ("Limit".to_string(), Some(vec![body, var, point]))
1407            }
1408            ExprTree::Series {
1409                body,
1410                var,
1411                point,
1412                order,
1413            } => ("Series".to_string(), Some(vec![body, var, point, order])),
1414            ExprTree::LaplaceTransform { body, t, s } => {
1415                ("LaplaceTransform".to_string(), Some(vec![body, t, s]))
1416            }
1417            ExprTree::InverseLaplaceTransform { body, s, t } => (
1418                "InverseLaplaceTransform".to_string(),
1419                Some(vec![body, s, t]),
1420            ),
1421            ExprTree::Residue { body, var, point } => {
1422                ("Residue".to_string(), Some(vec![body, var, point]))
1423            }
1424            ExprTree::RootOf { poly, index } => two("RootOf", poly, index),
1425            ExprTree::DSolve { expr, func, var } => {
1426                ("DSolve".to_string(), Some(vec![expr, func, var]))
1427            }
1428            ExprTree::RootSum { poly, body, sumvar } => {
1429                ("RootSum".to_string(), Some(vec![poly, body, sumvar]))
1430            }
1431            ExprTree::ConditionSet { var, condition } => two("ConditionSet", var, condition),
1432        }
1433    }
1434
1435    /// Extra literal arguments printed after the children in `to_srepr`
1436    /// (the open/closed flags of an `Interval`).
1437    fn srepr_trailing(&self) -> Option<String> {
1438        match self {
1439            ExprTree::Interval { flags, .. } => {
1440                Some(format!(", {}, {}", flags & 0x01 != 0, flags & 0x02 != 0))
1441            }
1442            _ => None,
1443        }
1444    }
1445
1446    /// SymPy-`srepr`-style constructor form of this tree — unambiguous and
1447    /// total (every variant prints).  See [`Ex::to_srepr`](crate::api::expr::Ex::to_srepr).
1448    ///
1449    /// ```
1450    /// use symplex::tree::ExprTree;
1451    ///
1452    /// let t = ExprTree::Pow {
1453    ///     base: Box::new(ExprTree::Symbol { name: "x".into() }),
1454    ///     exp: Box::new(ExprTree::Num { numer: "1".into(), denom: "2".into() }),
1455    /// };
1456    /// assert_eq!(t.to_srepr(), "Pow(Symbol('x'), Rational(1, 2))");
1457    /// ```
1458    #[must_use]
1459    pub fn to_srepr(&self) -> String {
1460        enum Item<'t> {
1461            Text(String),
1462            Node(&'t ExprTree),
1463        }
1464        let mut out = String::new();
1465        let mut stack: Vec<Item<'_>> = vec![Item::Node(self)];
1466        while let Some(item) = stack.pop() {
1467            match item {
1468                Item::Text(s) => out.push_str(&s),
1469                Item::Node(node) => {
1470                    let (head, children) = node.head_and_children();
1471                    out.push_str(&head);
1472                    let Some(children) = children else { continue };
1473                    out.push('(');
1474                    // Pushed in reverse so that popping yields left-to-right.
1475                    let mut close = String::new();
1476                    if let Some(trailing) = node.srepr_trailing() {
1477                        close.push_str(&trailing);
1478                    }
1479                    close.push(')');
1480                    stack.push(Item::Text(close));
1481                    let pairwise = matches!(node, ExprTree::Piecewise { .. });
1482                    for (i, child) in children.iter().enumerate().rev() {
1483                        if pairwise {
1484                            // `Piecewise((v1, c1), (v2, c2))`
1485                            if i % 2 == 1 {
1486                                stack.push(Item::Text(")".into()));
1487                                stack.push(Item::Node(child));
1488                                stack.push(Item::Text(", ".into()));
1489                            } else {
1490                                stack.push(Item::Node(child));
1491                                stack.push(Item::Text(if i == 0 { "(" } else { ", (" }.into()));
1492                            }
1493                        } else {
1494                            stack.push(Item::Node(child));
1495                            if i > 0 {
1496                                stack.push(Item::Text(", ".into()));
1497                            }
1498                        }
1499                    }
1500                }
1501            }
1502        }
1503        out
1504    }
1505
1506    /// Graphviz `digraph` of this tree (SymPy: `dotprint`).  See
1507    /// [`Ex::to_dot`](crate::api::expr::Ex::to_dot).
1508    ///
1509    /// Node ids are assigned in pre-order (`n0` is the root, children
1510    /// left to right), so the output is deterministic; labels are the
1511    /// node kind with its value for atoms (`Symbol('x')`, `Integer(2)`).
1512    #[must_use]
1513    pub fn to_dot(&self) -> String {
1514        let mut nodes: Vec<String> = Vec::new();
1515        let mut edges: Vec<String> = Vec::new();
1516        // (node, parent id); pre-order with children pushed in reverse.
1517        let mut stack: Vec<(&ExprTree, Option<usize>)> = vec![(self, None)];
1518        while let Some((node, parent)) = stack.pop() {
1519            let id = nodes.len();
1520            let (head, children) = node.head_and_children();
1521            let label = match node {
1522                ExprTree::Interval { flags, .. } => format!(
1523                    "Interval('{}{}')",
1524                    if flags & 0x01 != 0 { '(' } else { '[' },
1525                    if flags & 0x02 != 0 { ')' } else { ']' }
1526                ),
1527                _ => head,
1528            };
1529            nodes.push(format!(
1530                "    n{id} [label=\"{}\"];",
1531                label.replace('\\', "\\\\").replace('"', "\\\"")
1532            ));
1533            if let Some(p) = parent {
1534                edges.push(format!("    n{p} -> n{id};"));
1535            }
1536            if let Some(children) = children {
1537                for child in children.into_iter().rev() {
1538                    stack.push((child, Some(id)));
1539                }
1540            }
1541        }
1542        let mut out = String::from("digraph {\n    ordering=out;\n    rankdir=TD;\n");
1543        for n in &nodes {
1544            out.push_str(n);
1545            out.push('\n');
1546        }
1547        for e in &edges {
1548            out.push_str(e);
1549            out.push('\n');
1550        }
1551        out.push_str("}\n");
1552        out
1553    }
1554}
1555
1556impl<S: crate::api::expr::Sort> crate::api::expr::Expr<S> {
1557    /// SymPy-`srepr`-style constructor form: an unambiguous, parseable-by-eye
1558    /// rendering of the exact tree, derived from [`to_tree`](Self::to_tree)
1559    /// so it is total (SymPy: `srepr(expr)`).
1560    ///
1561    /// Atoms print as `Integer(2)`, `Rational(1, 2)`, `Symbol('x')`, `pi`,
1562    /// `E`, `I`, `oo`; compound nodes as `Head(child, …)` with SymPy's
1563    /// heads where they exist (`Add`, `Mul`, `Pow`, `sin`, `log`, `Abs`,
1564    /// `StrictGreaterThan`, `Interval(a, b, false, true)`, …) and symplex's
1565    /// otherwise (`Neg`, `DefiniteIntegral(f, x, a, b)`).  Library and
1566    /// user functions print as `name(args)`.  Children appear in the
1567    /// arena's canonical order (numbers first in a sum), not display order:
1568    /// this is the exact tree, as `to_tree`/`to_json` see it.
1569    ///
1570    /// ```
1571    /// use symplex::prelude::*;
1572    ///
1573    /// let ctx = Context::new();
1574    /// let x = ctx.symbol("x");
1575    /// assert_eq!((2 * &x + 1).to_srepr(), "Add(Integer(1), Mul(Integer(2), Symbol('x')))");
1576    /// assert_eq!((&x / 2).to_srepr(), "Mul(Rational(1, 2), Symbol('x'))");
1577    /// assert_eq!(x.sin().powi(2).to_srepr(), "Pow(sin(Symbol('x')), Integer(2))");
1578    /// assert_eq!(x.gt(&ctx.int(0)).to_srepr(), "StrictGreaterThan(Symbol('x'), Integer(0))");
1579    /// ```
1580    #[must_use = "returns the rendered string; does not modify in place"]
1581    pub fn to_srepr(&self) -> String {
1582        self.to_tree().to_srepr()
1583    }
1584
1585    /// Graphviz DOT source for the expression tree (SymPy: `dotprint`).
1586    ///
1587    /// One node per tree position (labelled with the node kind and, for
1588    /// atoms, the value), one edge per child, ids `n0`, `n1`, … assigned
1589    /// in pre-order so the output is deterministic.  Render with
1590    /// `dot -Tsvg`.
1591    ///
1592    /// ```
1593    /// use symplex::prelude::*;
1594    ///
1595    /// let ctx = Context::new();
1596    /// let x = ctx.symbol("x");
1597    /// assert_eq!(
1598    ///     (2 * &x + 1).to_dot(),
1599    ///     "digraph {\n\
1600    ///     \x20   ordering=out;\n\
1601    ///     \x20   rankdir=TD;\n\
1602    ///     \x20   n0 [label=\"Add\"];\n\
1603    ///     \x20   n1 [label=\"Integer(1)\"];\n\
1604    ///     \x20   n2 [label=\"Mul\"];\n\
1605    ///     \x20   n3 [label=\"Integer(2)\"];\n\
1606    ///     \x20   n4 [label=\"Symbol('x')\"];\n\
1607    ///     \x20   n0 -> n1;\n\
1608    ///     \x20   n0 -> n2;\n\
1609    ///     \x20   n2 -> n3;\n\
1610    ///     \x20   n2 -> n4;\n\
1611    ///     }\n"
1612    /// );
1613    /// ```
1614    #[must_use = "returns the rendered string; does not modify in place"]
1615    pub fn to_dot(&self) -> String {
1616        self.to_tree().to_dot()
1617    }
1618}
1619
1620// ═══════════════════════════════════════════════════════════════════════════
1621// Tests
1622// ═══════════════════════════════════════════════════════════════════════════
1623
1624#[cfg(test)]
1625mod tests {
1626    use super::*;
1627    use crate::base::arena::Arena;
1628
1629    fn display(a: &Arena, id: ExprId) -> String {
1630        a.display(id).to_string()
1631    }
1632
1633    #[test]
1634    fn roundtrip_integer() {
1635        let mut a = Arena::new();
1636        let expr = a.int(42);
1637        let tree = expr_to_tree(&a, expr);
1638        let back = tree_to_expr(&mut a, &tree);
1639        assert_eq!(display(&a, back), "42");
1640    }
1641
1642    #[test]
1643    fn roundtrip_rational() {
1644        let mut a = Arena::new();
1645        let expr = a.rational(3, 7);
1646        let tree = expr_to_tree(&a, expr);
1647        let back = tree_to_expr(&mut a, &tree);
1648        assert_eq!(display(&a, back), "3/7");
1649    }
1650
1651    #[test]
1652    fn roundtrip_symbol() {
1653        let mut a = Arena::new();
1654        let expr = a.symbol("x");
1655        let tree = expr_to_tree(&a, expr);
1656        let back = tree_to_expr(&mut a, &tree);
1657        assert_eq!(display(&a, back), "x");
1658    }
1659
1660    #[test]
1661    fn roundtrip_polynomial() {
1662        let mut a = Arena::new();
1663        let x = a.symbol("x");
1664        let two = a.int(2);
1665        let x2 = a.pow(x, two);
1666        let two_id = a.int(2);
1667        let two_x = a.mul(&[two_id, x]);
1668        let one = a.one;
1669        let expr = a.add(&[x2, two_x, one]);
1670        let tree = expr_to_tree(&a, expr);
1671        let back = tree_to_expr(&mut a, &tree);
1672        assert_eq!(display(&a, expr), display(&a, back));
1673    }
1674
1675    #[test]
1676    fn roundtrip_sin() {
1677        let mut a = Arena::new();
1678        let x = a.symbol("x");
1679        let expr = a.sin(x);
1680        let tree = expr_to_tree(&a, expr);
1681        let back = tree_to_expr(&mut a, &tree);
1682        assert_eq!(display(&a, back), "sin(x)");
1683    }
1684
1685    #[test]
1686    fn roundtrip_pi() {
1687        let a = Arena::new();
1688        let tree = expr_to_tree(&a, a.pi);
1689        let mut a2 = Arena::new();
1690        let back = tree_to_expr(&mut a2, &tree);
1691        assert_eq!(display(&a2, back), "pi");
1692    }
1693
1694    #[test]
1695    fn json_roundtrip() {
1696        let mut a = Arena::new();
1697        let x = a.symbol("x");
1698        let two = a.int(2);
1699        let expr = a.pow(x, two);
1700        let tree = expr_to_tree(&a, expr);
1701        let json = serde_json::to_string(&tree).unwrap();
1702        let tree2: ExprTree = serde_json::from_str(&json).unwrap();
1703        assert_eq!(tree, tree2);
1704        let back = tree_to_expr(&mut a, &tree2);
1705        assert_eq!(display(&a, back), "x^2");
1706    }
1707
1708    #[test]
1709    fn json_deserialize_from_scratch() {
1710        // Simulate receiving JSON from an external source
1711        let json = r#"{"type":"Add","terms":[{"type":"Num","numer":"1","denom":"1"},{"type":"Symbol","name":"x"}]}"#;
1712        let tree: ExprTree = serde_json::from_str(json).unwrap();
1713        let mut a = Arena::new();
1714        let expr = tree_to_expr(&mut a, &tree);
1715        assert_eq!(display(&a, expr), "x + 1");
1716    }
1717
1718    #[test]
1719    fn roundtrip_all_functions() {
1720        let mut a = Arena::new();
1721        let x = a.symbol("x");
1722        let funcs = [
1723            a.sin(x),
1724            a.cos(x),
1725            a.tan(x),
1726            a.exp(x),
1727            a.ln(x),
1728            a.sqrt(x),
1729            a.abs(x),
1730            a.asin(x),
1731            a.acos(x),
1732            a.atan(x),
1733            a.sinh(x),
1734            a.cosh(x),
1735            a.tanh(x),
1736        ];
1737        for &expr in &funcs {
1738            let tree = expr_to_tree(&a, expr);
1739            let back = tree_to_expr(&mut a, &tree);
1740            assert_eq!(
1741                display(&a, expr),
1742                display(&a, back),
1743                "roundtrip failed for {}",
1744                display(&a, expr)
1745            );
1746        }
1747    }
1748
1749    #[test]
1750    fn roundtrip_derivative() {
1751        let mut a = Arena::new();
1752        let x = a.symbol("x");
1753        let body = a.sin(x);
1754        let expr = a.intern(ExprNode::Derivative(body, x));
1755        let tree = expr_to_tree(&a, expr);
1756        let back = tree_to_expr(&mut a, &tree);
1757        assert_eq!(display(&a, back), "Derivative(sin(x), x)");
1758    }
1759
1760    #[test]
1761    fn roundtrip_integral() {
1762        let mut a = Arena::new();
1763        let x = a.symbol("x");
1764        let body = a.sin(x);
1765        let expr = a.intern(ExprNode::Integral(body, x));
1766        let tree = expr_to_tree(&a, expr);
1767        let back = tree_to_expr(&mut a, &tree);
1768        assert_eq!(display(&a, back), "Integral(sin(x), x)");
1769    }
1770
1771    #[test]
1772    fn roundtrip_definite_integral() {
1773        let mut a = Arena::new();
1774        let x = a.symbol("x");
1775        let body = a.pow(x, x);
1776        let one = a.one;
1777        let expr = a.definite_integral(body, x, a.zero, one);
1778        let tree = expr_to_tree(&a, expr);
1779        let json = serde_json::to_string(&tree).unwrap();
1780        let tree2: ExprTree = serde_json::from_str(&json).unwrap();
1781        assert_eq!(tree2, tree);
1782        assert_eq!(tree_to_expr(&mut a, &tree2), expr);
1783        assert_eq!(display(&a, expr), "Integral(x^x, x, 0, 1)");
1784    }
1785
1786    // ── 0.2 nodes ──────────────────────────────────────────────────────────
1787
1788    #[test]
1789    fn roundtrip_named_constants() {
1790        let mut a = Arena::new();
1791        for id in [a.euler_gamma, a.catalan, a.golden_ratio] {
1792            let tree = expr_to_tree(&a, id);
1793            let json = serde_json::to_string(&tree).unwrap();
1794            let tree2: ExprTree = serde_json::from_str(&json).unwrap();
1795            assert_eq!(tree2, tree);
1796            assert_eq!(tree_to_expr(&mut a, &tree2), id);
1797        }
1798        assert_eq!(expr_to_tree(&a, a.euler_gamma), ExprTree::EulerGamma);
1799        assert_eq!(expr_to_tree(&a, a.catalan), ExprTree::Catalan);
1800        assert_eq!(expr_to_tree(&a, a.golden_ratio), ExprTree::GoldenRatio);
1801    }
1802
1803    #[test]
1804    fn roundtrip_complex_and_special_nodes() {
1805        let mut a = Arena::new();
1806        let x = a.symbol("x");
1807        let n = a.symbol("n");
1808        let nodes = [
1809            a.intern(ExprNode::Re(x)),
1810            a.intern(ExprNode::Im(x)),
1811            a.intern(ExprNode::Conjugate(x)),
1812            a.intern(ExprNode::Arg(x)),
1813            a.intern(ExprNode::Si(x)),
1814            a.intern(ExprNode::Ci(x)),
1815            a.intern(ExprNode::Ei(x)),
1816            a.intern(ExprNode::Li(x)),
1817            a.intern(ExprNode::Zeta(x)),
1818            a.intern(ExprNode::Polygamma(n, x)),
1819            a.intern(ExprNode::KroneckerDelta(n, x)),
1820        ];
1821        for id in nodes {
1822            let tree = expr_to_tree(&a, id);
1823            let json = serde_json::to_string(&tree).unwrap();
1824            let tree2: ExprTree = serde_json::from_str(&json).unwrap();
1825            let back = tree_to_expr(&mut a, &tree2);
1826            assert_eq!(back, id, "round trip of {}", display(&a, id));
1827        }
1828    }
1829
1830    #[test]
1831    fn tree_to_expr_uses_canonical_constructors() {
1832        // Deserialising `Zeta(2)` folds to π²/6, like the constructor does.
1833        let mut a = Arena::new();
1834        let two = a.int(2);
1835        let tree = ExprTree::Zeta {
1836            arg: Box::new(expr_to_tree(&a, two)),
1837        };
1838        let id = tree_to_expr(&mut a, &tree);
1839        assert_eq!(display(&a, id), "1/6*pi^2");
1840    }
1841}