Skip to main content

laddu_expr/
expression.rs

1use std::{
2    collections::{HashMap, HashSet},
3    fmt,
4    hash::Hash,
5    sync::{Arc, OnceLock},
6};
7
8use num::complex::Complex64;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    ExprGraphError, ExprShapeError, ParamError, ParamResult,
13    parameters::{InitialSpec, ParamState, Parameter},
14};
15
16/// Stable identifier for a node in a serialized [`ExprGraph`].
17#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18pub struct ExprId(u64);
19
20impl ExprId {
21    /// Creates an identifier from a zero-based node index.
22    pub fn from_index(index: usize) -> Self {
23        Self(index as u64)
24    }
25
26    /// Returns the zero-based node index.
27    pub fn index(self) -> usize {
28        self.0 as usize
29    }
30}
31
32/// Runtime value category produced by an expression node.
33#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum ValueKind {
35    /// A real scalar.
36    Real,
37    /// A complex scalar.
38    Complex,
39    /// A vector with a fixed number of elements.
40    Vector {
41        /// Number of vector elements.
42        len: usize,
43    },
44    /// A matrix with fixed dimensions.
45    Matrix {
46        /// Number of rows.
47        rows: usize,
48        /// Number of columns.
49        cols: usize,
50    },
51}
52
53/// Statically known scalar number category.
54#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
55pub enum NumberClass {
56    /// No narrower category is known.
57    Unknown,
58    /// The value is real.
59    Real,
60    /// The value is purely imaginary.
61    Imaginary,
62    /// The value may have real and imaginary components.
63    Complex,
64}
65
66/// Context-free value semantics inferred for one expression node.
67#[derive(Copy, Clone, Debug, PartialEq, Eq)]
68pub struct ExprNodeSemantics {
69    /// Runtime value kind.
70    pub value_kind: ValueKind,
71    /// Known relationship between real and imaginary components.
72    pub number_class: NumberClass,
73}
74
75fn add_number_class(lhs: NumberClass, rhs: NumberClass) -> NumberClass {
76    use NumberClass::{Complex, Imaginary, Real, Unknown};
77    match (lhs, rhs) {
78        (Real, Real) => Real,
79        (Imaginary, Imaginary) => Imaginary,
80        (Complex, _) | (_, Complex) => Complex,
81        (Unknown, _) | (_, Unknown) => Unknown,
82        _ => Complex,
83    }
84}
85
86fn mul_number_class(lhs: NumberClass, rhs: NumberClass) -> NumberClass {
87    use NumberClass::{Complex, Imaginary, Real, Unknown};
88    match (lhs, rhs) {
89        (Real, Real) | (Imaginary, Imaginary) => Real,
90        (Real, Imaginary) | (Imaginary, Real) => Imaginary,
91        (Complex, _) | (_, Complex) => Complex,
92        (Unknown, _) | (_, Unknown) => Unknown,
93    }
94}
95
96/// Intrinsic source of a node's evaluation dependencies.
97#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
98pub enum ExprDependencyKind {
99    /// The node is a compile-time constant.
100    Constant,
101    /// The node directly reads a parameter definition.
102    Parameter,
103    /// The node directly reads event data.
104    Event,
105    /// The node inherits the union of its children's dependencies.
106    Children,
107}
108
109/// Structural shape of an expression.
110#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub enum ExprShape {
112    /// A scalar expression.
113    Scalar,
114    /// A vector expression.
115    Vector {
116        /// Number of vector elements.
117        len: usize,
118    },
119    /// A matrix expression.
120    Matrix {
121        /// Number of rows.
122        rows: usize,
123        /// Number of columns.
124        cols: usize,
125    },
126}
127
128impl fmt::Display for ExprShape {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::Scalar => write!(f, "scalar"),
132            Self::Vector { len } => write!(f, "vector[{len}]"),
133            Self::Matrix { rows, cols } => write!(f, "matrix[{rows}x{cols}]"),
134        }
135    }
136}
137
138/// Converts a component selector into a zero-based index.
139pub trait ComponentIndex {
140    /// Returns the selected zero-based component index.
141    fn component_index(self) -> usize;
142}
143
144impl ComponentIndex for usize {
145    fn component_index(self) -> usize {
146        self
147    }
148}
149
150impl ComponentIndex for i32 {
151    fn component_index(self) -> usize {
152        usize::try_from(self).expect("component index must be nonnegative")
153    }
154}
155
156#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
157/// A named component of a four-momentum in `(E, px, py, pz)` order.
158pub enum P4Component {
159    /// Energy.
160    E,
161    /// Momentum in the x direction.
162    Px,
163    /// Momentum in the y direction.
164    Py,
165    /// Momentum in the z direction.
166    Pz,
167}
168
169impl P4Component {
170    /// Return the lowercase event-column suffix for this component.
171    pub fn label(self) -> &'static str {
172        match self {
173            Self::E => "e",
174            Self::Px => "px",
175            Self::Py => "py",
176            Self::Pz => "pz",
177        }
178    }
179
180    /// Return the component's position in `(E, px, py, pz)` order.
181    pub fn index(self) -> usize {
182        match self {
183            Self::E => 0,
184            Self::Px => 1,
185            Self::Py => 2,
186            Self::Pz => 3,
187        }
188    }
189}
190
191/// Unary operation in an expression graph.
192#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
193pub enum UnaryOp {
194    /// Arithmetic negation.
195    Neg,
196    /// Real part.
197    Real,
198    /// Imaginary part.
199    Imag,
200    /// Complex conjugate.
201    Conj,
202    /// Squared complex norm.
203    NormSqr,
204    /// Principal square root.
205    Sqrt,
206    /// Exponential.
207    Exp,
208    /// Sine.
209    Sin,
210    /// Cosine.
211    Cos,
212    /// Natural logarithm.
213    Log,
214    /// Integer power.
215    PowI(i32),
216}
217
218impl UnaryOp {
219    /// Applies this operation to a scalar complex value.
220    pub fn evaluate(&self, value: Complex64) -> Complex64 {
221        match self {
222            Self::Neg => -value,
223            Self::Real => Complex64::from(value.re),
224            Self::Imag => Complex64::from(value.im),
225            Self::Conj => value.conj(),
226            Self::NormSqr => Complex64::from(value.norm_sqr()),
227            Self::Sqrt => value.sqrt(),
228            Self::Exp => value.exp(),
229            Self::Sin => value.sin(),
230            Self::Cos => value.cos(),
231            Self::Log => value.ln(),
232            Self::PowI(power) => value.powi(*power),
233        }
234    }
235}
236
237/// Binary operation in an expression graph.
238#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
239pub enum BinaryOp {
240    /// Addition.
241    Add,
242    /// Subtraction.
243    Sub,
244    /// Multiplication.
245    Mul,
246    /// Division.
247    Div,
248    /// Two-argument arctangent of the real parts.
249    Atan2,
250}
251
252impl BinaryOp {
253    /// Applies this operation to two scalar complex values.
254    pub fn evaluate(&self, a: Complex64, b: Complex64) -> Complex64 {
255        match self {
256            Self::Add => a + b,
257            Self::Sub => a - b,
258            Self::Mul => a * b,
259            Self::Div => a / b,
260            Self::Atan2 => Complex64::from(a.re.atan2(b.re)),
261        }
262    }
263}
264
265/// Serialized node in a topologically ordered [`ExprGraph`].
266#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
267pub enum ExprNode {
268    /// A real constant.
269    RealConst(f64),
270    /// A complex constant.
271    ComplexConst(Complex64),
272    /// A scalar fit parameter.
273    ScalarParam(Parameter),
274    /// A named scalar event column.
275    EventScalar(Arc<str>),
276    /// One component of a named event four-momentum.
277    EventP4Component {
278        /// Event column base name.
279        name: Arc<str>,
280        /// Requested four-momentum component.
281        component: P4Component,
282    },
283    /// A unary operation.
284    Unary {
285        /// Operation to apply.
286        op: UnaryOp,
287        /// Input node.
288        input: ExprId,
289    },
290    /// A binary operation.
291    Binary {
292        /// Operation to apply.
293        op: BinaryOp,
294        /// Left operand.
295        lhs: ExprId,
296        /// Right operand.
297        rhs: ExprId,
298    },
299    /// A sum of zero or more terms.
300    NaryAdd {
301        /// Term nodes.
302        terms: Vec<ExprId>,
303    },
304    /// A product of zero or more factors.
305    NaryMul {
306        /// Factor nodes.
307        factors: Vec<ExprId>,
308    },
309    /// A complex scalar assembled from real and imaginary expressions.
310    Complex {
311        /// Real component.
312        re: ExprId,
313        /// Imaginary component.
314        im: ExprId,
315    },
316    /// A vector assembled from scalar elements.
317    Vector {
318        /// Scalar element nodes.
319        elements: Vec<ExprId>,
320    },
321    /// A row-major matrix assembled from scalar elements.
322    Matrix {
323        /// Number of rows.
324        rows: usize,
325        /// Number of columns.
326        cols: usize,
327        /// Row-major scalar elements.
328        elements: Vec<ExprId>,
329    },
330    /// A vector component selection.
331    Component {
332        /// Vector input.
333        input: ExprId,
334        /// Zero-based component index.
335        index: usize,
336    },
337    /// A matrix element selection.
338    MatrixElement {
339        /// Matrix input.
340        input: ExprId,
341        /// Zero-based row index.
342        row: usize,
343        /// Zero-based column index.
344        col: usize,
345    },
346    /// Matrix-matrix multiplication.
347    MatMul {
348        /// Left matrix.
349        lhs: ExprId,
350        /// Right matrix.
351        rhs: ExprId,
352    },
353    /// Matrix-vector multiplication.
354    MatVec {
355        /// Matrix operand.
356        matrix: ExprId,
357        /// Vector operand.
358        vector: ExprId,
359    },
360    /// Vector dot product.
361    Dot {
362        /// Left vector.
363        lhs: ExprId,
364        /// Right vector.
365        rhs: ExprId,
366    },
367    /// Solution of a linear system.
368    Solve {
369        /// Coefficient matrix.
370        matrix: ExprId,
371        /// Right-hand-side vector or matrix.
372        rhs: ExprId,
373    },
374}
375
376/// Bit-exact structural identity for a scalar parameter definition.
377///
378/// Equality includes state, initial-value policy, bounds, periodicity, scale,
379/// and user-facing labels. Floating-point values are compared by their bit
380/// patterns, so signed zero and distinct NaN payloads remain distinct.
381#[doc(hidden)]
382#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
383pub struct ParameterStructuralKey {
384    name: Arc<str>,
385    state: ParameterStateStructuralKey,
386    initial: InitialStructuralKey,
387    bounds: (Option<u64>, Option<u64>),
388    periodic: bool,
389    scale: Option<u64>,
390    unit: Option<Arc<str>>,
391    latex: Option<Arc<str>>,
392    description: Option<Arc<str>>,
393}
394
395#[doc(hidden)]
396#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
397pub enum ParameterStateStructuralKey {
398    Free,
399    Fixed(u64),
400}
401
402#[doc(hidden)]
403#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
404pub enum InitialStructuralKey {
405    Default,
406    Value(u64),
407    Uniform { min: u64, max: u64 },
408}
409
410/// Bit-exact, metadata-free structural identity for an expression node.
411///
412/// The key includes the node variant, semantic payload, child identifiers, and
413/// complete parameter definitions. It deliberately excludes [`ExprMetadata`].
414/// Its ordering is deterministic but its representation and hash values are an
415/// internal workspace contract, not a stable serialized or persisted format.
416#[doc(hidden)]
417#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
418pub enum ExprNodeStructuralKey {
419    RealConst(u64),
420    ComplexConst {
421        re: u64,
422        im: u64,
423    },
424    ScalarParam(ParameterStructuralKey),
425    EventScalar(Arc<str>),
426    EventP4Component {
427        name: Arc<str>,
428        component: P4Component,
429    },
430    Unary {
431        op: UnaryOp,
432        input: ExprId,
433    },
434    Binary {
435        op: BinaryOp,
436        lhs: ExprId,
437        rhs: ExprId,
438    },
439    NaryAdd {
440        terms: Vec<ExprId>,
441    },
442    NaryMul {
443        factors: Vec<ExprId>,
444    },
445    Complex {
446        re: ExprId,
447        im: ExprId,
448    },
449    Vector {
450        elements: Vec<ExprId>,
451    },
452    Matrix {
453        rows: usize,
454        cols: usize,
455        elements: Vec<ExprId>,
456    },
457    Component {
458        input: ExprId,
459        index: usize,
460    },
461    MatrixElement {
462        input: ExprId,
463        row: usize,
464        col: usize,
465    },
466    MatMul {
467        lhs: ExprId,
468        rhs: ExprId,
469    },
470    MatVec {
471        matrix: ExprId,
472        vector: ExprId,
473    },
474    Dot {
475        lhs: ExprId,
476        rhs: ExprId,
477    },
478    Solve {
479        matrix: ExprId,
480        rhs: ExprId,
481    },
482}
483
484impl From<&Parameter> for ParameterStructuralKey {
485    fn from(parameter: &Parameter) -> Self {
486        let state = match parameter.state() {
487            ParamState::Free => ParameterStateStructuralKey::Free,
488            ParamState::Fixed(value) => ParameterStateStructuralKey::Fixed(value.to_bits()),
489        };
490        let initial = match parameter.initial_spec() {
491            InitialSpec::Default => InitialStructuralKey::Default,
492            InitialSpec::Value(value) => InitialStructuralKey::Value(value.to_bits()),
493            InitialSpec::Uniform { min, max } => InitialStructuralKey::Uniform {
494                min: min.to_bits(),
495                max: max.to_bits(),
496            },
497        };
498        Self {
499            name: Arc::from(parameter.name()),
500            state,
501            initial,
502            bounds: (
503                parameter.bounds_spec().min.map(f64::to_bits),
504                parameter.bounds_spec().max.map(f64::to_bits),
505            ),
506            periodic: parameter.is_periodic(),
507            scale: parameter.scale().map(f64::to_bits),
508            unit: parameter.unit_label().map(Arc::from),
509            latex: parameter.latex_label().map(Arc::from),
510            description: parameter.description_text().map(Arc::from),
511        }
512    }
513}
514
515impl From<Complex64> for ExprNode {
516    fn from(value: Complex64) -> Self {
517        if value.im == 0.0 {
518            Self::RealConst(value.re)
519        } else {
520            Self::ComplexConst(value)
521        }
522    }
523}
524
525impl ExprNode {
526    /// Infers this node's context-free value semantics from the already
527    /// computed semantics of earlier nodes in the expression graph.
528    pub fn semantics(&self, children: &[ExprNodeSemantics]) -> ExprNodeSemantics {
529        ExprNodeSemantics {
530            value_kind: self.infer_value_kind(children),
531            number_class: self.infer_number_class(children),
532        }
533    }
534
535    fn infer_value_kind(&self, children: &[ExprNodeSemantics]) -> ValueKind {
536        match self {
537            Self::RealConst(_) | Self::ScalarParam(_) => ValueKind::Real,
538            Self::ComplexConst(value) => {
539                if value.im == 0.0 {
540                    ValueKind::Real
541                } else {
542                    ValueKind::Complex
543                }
544            }
545            Self::EventScalar(_) | Self::EventP4Component { .. } => ValueKind::Real,
546            Self::Unary { op, input } => match op {
547                UnaryOp::Real | UnaryOp::Imag | UnaryOp::NormSqr => ValueKind::Real,
548                UnaryOp::Neg
549                | UnaryOp::Conj
550                | UnaryOp::Sqrt
551                | UnaryOp::Exp
552                | UnaryOp::Sin
553                | UnaryOp::Cos
554                | UnaryOp::Log
555                | UnaryOp::PowI(_) => children[input.index()].value_kind,
556            },
557            Self::Binary { op, lhs, rhs } => {
558                if *op == BinaryOp::Atan2 {
559                    return ValueKind::Real;
560                }
561                if children[lhs.index()].value_kind == ValueKind::Real
562                    && children[rhs.index()].value_kind == ValueKind::Real
563                {
564                    ValueKind::Real
565                } else {
566                    ValueKind::Complex
567                }
568            }
569            Self::NaryAdd { terms } => {
570                if terms
571                    .iter()
572                    .all(|id| children[id.index()].value_kind == ValueKind::Real)
573                {
574                    ValueKind::Real
575                } else {
576                    ValueKind::Complex
577                }
578            }
579            Self::NaryMul { factors } => {
580                if factors
581                    .iter()
582                    .all(|id| children[id.index()].value_kind == ValueKind::Real)
583                {
584                    ValueKind::Real
585                } else {
586                    ValueKind::Complex
587                }
588            }
589            Self::Complex { .. } => ValueKind::Complex,
590            Self::Vector { elements } => ValueKind::Vector {
591                len: elements.len(),
592            },
593            Self::Matrix { rows, cols, .. } => ValueKind::Matrix {
594                rows: *rows,
595                cols: *cols,
596            },
597            Self::Component { input, .. } => match children[input.index()].value_kind {
598                ValueKind::Vector { .. } => ValueKind::Complex,
599                kind => kind,
600            },
601            Self::MatrixElement { .. } | Self::Dot { .. } => ValueKind::Complex,
602            Self::MatMul { lhs, rhs } => {
603                let ValueKind::Matrix { rows, .. } = children[lhs.index()].value_kind else {
604                    return ValueKind::Complex;
605                };
606                let ValueKind::Matrix { cols, .. } = children[rhs.index()].value_kind else {
607                    return ValueKind::Complex;
608                };
609                ValueKind::Matrix { rows, cols }
610            }
611            Self::MatVec { matrix, .. } => {
612                let ValueKind::Matrix { rows, .. } = children[matrix.index()].value_kind else {
613                    return ValueKind::Complex;
614                };
615                ValueKind::Vector { len: rows }
616            }
617            Self::Solve { rhs, .. } => children[rhs.index()].value_kind,
618        }
619    }
620
621    fn infer_number_class(&self, children: &[ExprNodeSemantics]) -> NumberClass {
622        match self {
623            Self::RealConst(_) | Self::ScalarParam(_) => NumberClass::Real,
624            Self::ComplexConst(value) => match (value.re == 0.0, value.im == 0.0) {
625                (_, true) => NumberClass::Real,
626                (true, false) => NumberClass::Imaginary,
627                (false, false) => NumberClass::Complex,
628            },
629            Self::EventScalar(_) | Self::EventP4Component { .. } => NumberClass::Real,
630            Self::Unary { op, input } => match op {
631                UnaryOp::Neg | UnaryOp::Conj => children[input.index()].number_class,
632                UnaryOp::Real | UnaryOp::Imag | UnaryOp::NormSqr => NumberClass::Real,
633                UnaryOp::Exp | UnaryOp::Sin | UnaryOp::Cos | UnaryOp::PowI(_) => {
634                    let input = children[input.index()].number_class;
635                    if input == NumberClass::Real {
636                        NumberClass::Real
637                    } else {
638                        NumberClass::Unknown
639                    }
640                }
641                UnaryOp::Sqrt | UnaryOp::Log => NumberClass::Unknown,
642            },
643            Self::Binary { op, lhs, rhs } => {
644                let lhs = children[lhs.index()].number_class;
645                let rhs = children[rhs.index()].number_class;
646                match op {
647                    BinaryOp::Add | BinaryOp::Sub => add_number_class(lhs, rhs),
648                    BinaryOp::Mul | BinaryOp::Div => mul_number_class(lhs, rhs),
649                    BinaryOp::Atan2 => NumberClass::Real,
650                }
651            }
652            Self::NaryAdd { terms } => {
653                let mut classes = terms.iter().map(|id| children[id.index()].number_class);
654                let Some(first) = classes.next() else {
655                    return NumberClass::Real;
656                };
657                classes.fold(first, add_number_class)
658            }
659            Self::NaryMul { factors } => {
660                let mut classes = factors.iter().map(|id| children[id.index()].number_class);
661                let Some(first) = classes.next() else {
662                    return NumberClass::Real;
663                };
664                classes.fold(first, mul_number_class)
665            }
666            Self::Complex { .. } => NumberClass::Complex,
667            Self::Vector { .. }
668            | Self::Matrix { .. }
669            | Self::Component { .. }
670            | Self::MatrixElement { .. }
671            | Self::MatMul { .. }
672            | Self::MatVec { .. }
673            | Self::Dot { .. }
674            | Self::Solve { .. } => NumberClass::Unknown,
675        }
676    }
677
678    /// Returns the intrinsic source of this node's evaluation dependencies.
679    pub fn dependency_kind(&self) -> ExprDependencyKind {
680        match self {
681            Self::RealConst(_) | Self::ComplexConst(_) => ExprDependencyKind::Constant,
682            Self::ScalarParam(_) => ExprDependencyKind::Parameter,
683            Self::EventScalar(_) | Self::EventP4Component { .. } => ExprDependencyKind::Event,
684            _ => ExprDependencyKind::Children,
685        }
686    }
687
688    /// Returns this node's bit-exact, metadata-free structural identity.
689    #[doc(hidden)]
690    pub fn structural_key(&self) -> ExprNodeStructuralKey {
691        match self {
692            Self::RealConst(value) => ExprNodeStructuralKey::RealConst(value.to_bits()),
693            Self::ComplexConst(value) => ExprNodeStructuralKey::ComplexConst {
694                re: value.re.to_bits(),
695                im: value.im.to_bits(),
696            },
697            Self::ScalarParam(parameter) => {
698                ExprNodeStructuralKey::ScalarParam(ParameterStructuralKey::from(parameter))
699            }
700            Self::EventScalar(name) => ExprNodeStructuralKey::EventScalar(Arc::clone(name)),
701            Self::EventP4Component { name, component } => ExprNodeStructuralKey::EventP4Component {
702                name: Arc::clone(name),
703                component: *component,
704            },
705            Self::Unary { op, input } => ExprNodeStructuralKey::Unary {
706                op: *op,
707                input: *input,
708            },
709            Self::Binary { op, lhs, rhs } => ExprNodeStructuralKey::Binary {
710                op: *op,
711                lhs: *lhs,
712                rhs: *rhs,
713            },
714            Self::NaryAdd { terms } => ExprNodeStructuralKey::NaryAdd {
715                terms: terms.clone(),
716            },
717            Self::NaryMul { factors } => ExprNodeStructuralKey::NaryMul {
718                factors: factors.clone(),
719            },
720            Self::Complex { re, im } => ExprNodeStructuralKey::Complex { re: *re, im: *im },
721            Self::Vector { elements } => ExprNodeStructuralKey::Vector {
722                elements: elements.clone(),
723            },
724            Self::Matrix {
725                rows,
726                cols,
727                elements,
728            } => ExprNodeStructuralKey::Matrix {
729                rows: *rows,
730                cols: *cols,
731                elements: elements.clone(),
732            },
733            Self::Component { input, index } => ExprNodeStructuralKey::Component {
734                input: *input,
735                index: *index,
736            },
737            Self::MatrixElement { input, row, col } => ExprNodeStructuralKey::MatrixElement {
738                input: *input,
739                row: *row,
740                col: *col,
741            },
742            Self::MatMul { lhs, rhs } => ExprNodeStructuralKey::MatMul {
743                lhs: *lhs,
744                rhs: *rhs,
745            },
746            Self::MatVec { matrix, vector } => ExprNodeStructuralKey::MatVec {
747                matrix: *matrix,
748                vector: *vector,
749            },
750            Self::Dot { lhs, rhs } => ExprNodeStructuralKey::Dot {
751                lhs: *lhs,
752                rhs: *rhs,
753            },
754            Self::Solve { matrix, rhs } => ExprNodeStructuralKey::Solve {
755                matrix: *matrix,
756                rhs: *rhs,
757            },
758        }
759    }
760
761    /// Creates the most compact constant-node representation for `value`.
762    pub fn from_folded_const(value: Complex64) -> Self {
763        if value.im == 0.0 && value.im.is_sign_positive() {
764            Self::RealConst(value.re)
765        } else {
766            Self::ComplexConst(value)
767        }
768    }
769
770    /// Returns the node's scalar constant value, if it is a constant.
771    pub fn const_value(&self) -> Option<Complex64> {
772        match self {
773            ExprNode::RealConst(value) => Some(Complex64::from(*value)),
774            ExprNode::ComplexConst(value) => Some(*value),
775            _ => None,
776        }
777    }
778
779    /// Returns whether `node` is the scalar constant zero.
780    pub fn is_zero(node: &ExprNode) -> bool {
781        node.const_value()
782            .is_some_and(|value| value == Complex64::ZERO)
783    }
784
785    /// Returns whether `node` is the scalar constant one.
786    pub fn is_one(node: &ExprNode) -> bool {
787        node.const_value()
788            .is_some_and(|value| value == Complex64::ONE)
789    }
790
791    /// Iterates over this node's direct dependencies in semantic operand order.
792    ///
793    /// The iterator borrows the node and does not allocate. Binary operands are
794    /// returned left-to-right, and vector, matrix, sum, and product children
795    /// retain their stored order.
796    pub fn children(&self) -> impl ExactSizeIterator<Item = ExprId> + DoubleEndedIterator + '_ {
797        (0..self.child_count()).map(|index| self.child_at(index))
798    }
799
800    /// Returns the identifiers of this node's direct dependencies.
801    ///
802    /// This compatibility helper collects [`Self::children`]. Prefer the
803    /// borrowed iterator when an owned vector is not required.
804    pub fn child_ids(&self) -> Vec<ExprId> {
805        self.children().collect()
806    }
807
808    /// Returns a copy of this node with each direct dependency transformed.
809    ///
810    /// Children are passed to `map` in the same semantic order as
811    /// [`Self::children`]. Non-child fields are preserved exactly.
812    pub fn map_children(&self, mut map: impl FnMut(ExprId) -> ExprId) -> Self {
813        match self {
814            Self::RealConst(_)
815            | Self::ComplexConst(_)
816            | Self::ScalarParam(_)
817            | Self::EventScalar(_)
818            | Self::EventP4Component { .. } => self.clone(),
819            Self::Unary { op, input } => Self::Unary {
820                op: *op,
821                input: map(*input),
822            },
823            Self::Binary { op, lhs, rhs } => Self::Binary {
824                op: *op,
825                lhs: map(*lhs),
826                rhs: map(*rhs),
827            },
828            Self::NaryAdd { terms } => Self::NaryAdd {
829                terms: terms.iter().copied().map(&mut map).collect(),
830            },
831            Self::NaryMul { factors } => Self::NaryMul {
832                factors: factors.iter().copied().map(&mut map).collect(),
833            },
834            Self::Complex { re, im } => Self::Complex {
835                re: map(*re),
836                im: map(*im),
837            },
838            Self::Vector { elements } => Self::Vector {
839                elements: elements.iter().copied().map(&mut map).collect(),
840            },
841            Self::Matrix {
842                rows,
843                cols,
844                elements,
845            } => Self::Matrix {
846                rows: *rows,
847                cols: *cols,
848                elements: elements.iter().copied().map(&mut map).collect(),
849            },
850            Self::Component { input, index } => Self::Component {
851                input: map(*input),
852                index: *index,
853            },
854            Self::MatrixElement { input, row, col } => Self::MatrixElement {
855                input: map(*input),
856                row: *row,
857                col: *col,
858            },
859            Self::MatMul { lhs, rhs } => Self::MatMul {
860                lhs: map(*lhs),
861                rhs: map(*rhs),
862            },
863            Self::MatVec { matrix, vector } => Self::MatVec {
864                matrix: map(*matrix),
865                vector: map(*vector),
866            },
867            Self::Dot { lhs, rhs } => Self::Dot {
868                lhs: map(*lhs),
869                rhs: map(*rhs),
870            },
871            Self::Solve { matrix, rhs } => Self::Solve {
872                matrix: map(*matrix),
873                rhs: map(*rhs),
874            },
875        }
876    }
877
878    fn child_count(&self) -> usize {
879        match self {
880            Self::RealConst(_)
881            | Self::ComplexConst(_)
882            | Self::ScalarParam(_)
883            | Self::EventScalar(_)
884            | Self::EventP4Component { .. } => 0,
885            Self::Unary { .. } | Self::Component { .. } | Self::MatrixElement { .. } => 1,
886            Self::Binary { .. }
887            | Self::Complex { .. }
888            | Self::MatMul { .. }
889            | Self::MatVec { .. }
890            | Self::Dot { .. }
891            | Self::Solve { .. } => 2,
892            Self::NaryAdd { terms } => terms.len(),
893            Self::NaryMul { factors } => factors.len(),
894            Self::Vector { elements } | Self::Matrix { elements, .. } => elements.len(),
895        }
896    }
897
898    fn child_at(&self, index: usize) -> ExprId {
899        match self {
900            Self::Unary { input, .. }
901            | Self::Component { input, .. }
902            | Self::MatrixElement { input, .. } => *input,
903            Self::Binary { lhs, rhs, .. }
904            | Self::Complex { re: lhs, im: rhs }
905            | Self::MatMul { lhs, rhs }
906            | Self::Dot { lhs, rhs } => [*lhs, *rhs][index],
907            Self::MatVec { matrix, vector } => [*matrix, *vector][index],
908            Self::Solve { matrix, rhs } => [*matrix, *rhs][index],
909            Self::NaryAdd { terms } => terms[index],
910            Self::NaryMul { factors } => factors[index],
911            Self::Vector { elements } | Self::Matrix { elements, .. } => elements[index],
912            Self::RealConst(_)
913            | Self::ComplexConst(_)
914            | Self::ScalarParam(_)
915            | Self::EventScalar(_)
916            | Self::EventP4Component { .. } => unreachable!("leaf node has no children"),
917        }
918    }
919}
920
921/// Broad origin category recorded in [`ExprMetadata`].
922#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
923pub enum ExprSourceKind {
924    /// Constant literal.
925    Const,
926    /// Fit parameter.
927    Param,
928    /// Event data.
929    Event,
930    /// Unary operation.
931    Unary,
932    /// Binary or n-ary operation.
933    Binary,
934    /// Complex-number construction.
935    Complex,
936    /// Vector construction or selection.
937    Vector,
938    /// Matrix construction or selection.
939    Matrix,
940    /// Linear-algebra operation.
941    LinearAlgebra,
942}
943
944/// User-facing annotations and origin information for an expression node.
945#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
946pub struct ExprMetadata {
947    source: ExprSourceKind,
948    name: Option<Arc<str>>,
949    tags: Vec<Arc<str>>,
950}
951
952impl ExprMetadata {
953    /// Creates metadata for the given source category.
954    pub fn new(source: ExprSourceKind) -> Self {
955        Self {
956            source,
957            name: None,
958            tags: Vec::new(),
959        }
960    }
961
962    /// Returns the node's source category.
963    pub fn source(&self) -> ExprSourceKind {
964        self.source
965    }
966
967    /// Returns the optional user-assigned name.
968    pub fn name(&self) -> Option<&str> {
969        self.name.as_deref()
970    }
971
972    /// Returns the user-assigned tags.
973    pub fn tags(&self) -> &[Arc<str>] {
974        &self.tags
975    }
976
977    /// Returns whether the metadata contains `tag`.
978    pub fn has_tag(&self, tag: &str) -> bool {
979        self.tags.iter().any(|candidate| candidate.as_ref() == tag)
980    }
981}
982
983/// Shareable symbolic expression represented internally as a directed acyclic graph.
984#[derive(Clone, Debug)]
985pub struct Expr {
986    node: Arc<DagNode>,
987}
988
989#[derive(Clone, Debug)]
990struct DagNode {
991    kind: DagNodeKind,
992    metadata: ExprMetadata,
993    shape: OnceLock<Result<ExprShape, ExprShapeError>>,
994}
995
996#[derive(Clone, Debug)]
997enum DagNodeKind {
998    RealConst(f64),
999    ComplexConst(Complex64),
1000    ScalarParam(Parameter),
1001    EventScalar(Arc<str>),
1002    EventP4Component {
1003        name: Arc<str>,
1004        component: P4Component,
1005    },
1006    Unary {
1007        op: UnaryOp,
1008        input: Expr,
1009    },
1010    Binary {
1011        op: BinaryOp,
1012        lhs: Expr,
1013        rhs: Expr,
1014    },
1015    Complex {
1016        re: Expr,
1017        im: Expr,
1018    },
1019    Vector {
1020        elements: Vec<Expr>,
1021    },
1022    Matrix {
1023        rows: usize,
1024        cols: usize,
1025        elements: Vec<Expr>,
1026    },
1027    Component {
1028        input: Expr,
1029        index: usize,
1030    },
1031    MatrixElement {
1032        input: Expr,
1033        row: usize,
1034        col: usize,
1035    },
1036    MatMul {
1037        lhs: Expr,
1038        rhs: Expr,
1039    },
1040    MatVec {
1041        matrix: Expr,
1042        vector: Expr,
1043    },
1044    Dot {
1045        lhs: Expr,
1046        rhs: Expr,
1047    },
1048    Solve {
1049        matrix: Expr,
1050        rhs: Expr,
1051    },
1052}
1053
1054impl DagNodeKind {
1055    fn child_count(&self) -> usize {
1056        match self {
1057            Self::RealConst(_)
1058            | Self::ComplexConst(_)
1059            | Self::ScalarParam(_)
1060            | Self::EventScalar(_)
1061            | Self::EventP4Component { .. } => 0,
1062            Self::Unary { .. } | Self::Component { .. } | Self::MatrixElement { .. } => 1,
1063            Self::Binary { .. }
1064            | Self::Complex { .. }
1065            | Self::MatMul { .. }
1066            | Self::MatVec { .. }
1067            | Self::Dot { .. }
1068            | Self::Solve { .. } => 2,
1069            Self::Vector { elements } | Self::Matrix { elements, .. } => elements.len(),
1070        }
1071    }
1072
1073    fn child_at(&self, index: usize) -> &Expr {
1074        match self {
1075            Self::Unary { input, .. }
1076            | Self::Component { input, .. }
1077            | Self::MatrixElement { input, .. } => input,
1078            Self::Binary { lhs, rhs, .. } | Self::MatMul { lhs, rhs } | Self::Dot { lhs, rhs } => {
1079                [lhs, rhs][index]
1080            }
1081            Self::Complex { re, im } => [re, im][index],
1082            Self::MatVec { matrix, vector } => [matrix, vector][index],
1083            Self::Solve { matrix, rhs } => [matrix, rhs][index],
1084            Self::Vector { elements } | Self::Matrix { elements, .. } => &elements[index],
1085            Self::RealConst(_)
1086            | Self::ComplexConst(_)
1087            | Self::ScalarParam(_)
1088            | Self::EventScalar(_)
1089            | Self::EventP4Component { .. } => unreachable!("leaf nodes have no children"),
1090        }
1091    }
1092
1093    fn map_children(&self, mut map: impl FnMut(&Expr) -> Expr) -> Self {
1094        match self {
1095            Self::RealConst(value) => Self::RealConst(*value),
1096            Self::ComplexConst(value) => Self::ComplexConst(*value),
1097            Self::ScalarParam(parameter) => Self::ScalarParam(parameter.clone()),
1098            Self::EventScalar(name) => Self::EventScalar(Arc::clone(name)),
1099            Self::EventP4Component { name, component } => Self::EventP4Component {
1100                name: Arc::clone(name),
1101                component: *component,
1102            },
1103            Self::Unary { op, input } => Self::Unary {
1104                op: *op,
1105                input: map(input),
1106            },
1107            Self::Binary { op, lhs, rhs } => Self::Binary {
1108                op: *op,
1109                lhs: map(lhs),
1110                rhs: map(rhs),
1111            },
1112            Self::Complex { re, im } => Self::Complex {
1113                re: map(re),
1114                im: map(im),
1115            },
1116            Self::Vector { elements } => Self::Vector {
1117                elements: elements.iter().map(&mut map).collect(),
1118            },
1119            Self::Matrix {
1120                rows,
1121                cols,
1122                elements,
1123            } => Self::Matrix {
1124                rows: *rows,
1125                cols: *cols,
1126                elements: elements.iter().map(&mut map).collect(),
1127            },
1128            Self::Component { input, index } => Self::Component {
1129                input: map(input),
1130                index: *index,
1131            },
1132            Self::MatrixElement { input, row, col } => Self::MatrixElement {
1133                input: map(input),
1134                row: *row,
1135                col: *col,
1136            },
1137            Self::MatMul { lhs, rhs } => Self::MatMul {
1138                lhs: map(lhs),
1139                rhs: map(rhs),
1140            },
1141            Self::MatVec { matrix, vector } => Self::MatVec {
1142                matrix: map(matrix),
1143                vector: map(vector),
1144            },
1145            Self::Dot { lhs, rhs } => Self::Dot {
1146                lhs: map(lhs),
1147                rhs: map(rhs),
1148            },
1149            Self::Solve { matrix, rhs } => Self::Solve {
1150                matrix: map(matrix),
1151                rhs: map(rhs),
1152            },
1153        }
1154    }
1155}
1156
1157impl Expr {
1158    fn new(kind: DagNodeKind) -> Self {
1159        let source = source_kind(&kind);
1160        Self {
1161            node: Arc::new(DagNode {
1162                kind,
1163                metadata: ExprMetadata::new(source),
1164                shape: OnceLock::new(),
1165            }),
1166        }
1167    }
1168
1169    /// Assigns a display name to the expression root.
1170    pub fn named(self, name: impl Into<Arc<str>>) -> Self {
1171        self.with_metadata(|metadata| metadata.name = Some(name.into()))
1172    }
1173
1174    /// Adds a tag to the expression root.
1175    pub fn tagged(self, tag: impl Into<Arc<str>>) -> Self {
1176        let tag = tag.into();
1177        self.with_metadata(|metadata| {
1178            if !metadata.tags.iter().any(|existing| existing == &tag) {
1179                metadata.tags.push(tag);
1180            }
1181        })
1182    }
1183
1184    /// Adds each supplied tag to the expression root.
1185    pub fn tagged_with(self, tags: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Self {
1186        tags.into_iter().fold(self, Self::tagged)
1187    }
1188
1189    /// Replace tagged components that do not match any requested tag with zero.
1190    ///
1191    /// Untagged nodes remain active, while a matching tagged node retains its complete subtree.
1192    pub fn project_tags<'a>(&self, tags: impl IntoIterator<Item = &'a str>) -> Self {
1193        let tags: Vec<_> = tags.into_iter().collect();
1194        enum Frame {
1195            Visit(Expr),
1196            Rebuild(Expr, usize),
1197        }
1198
1199        let mut projected = Vec::new();
1200        let mut stack = vec![Frame::Visit(self.clone())];
1201        while let Some(frame) = stack.pop() {
1202            match frame {
1203                Frame::Visit(expr) => {
1204                    let has_tags = !expr.node.metadata.tags.is_empty();
1205                    if has_tags || expr.node.kind.child_count() == 0 {
1206                        projected.push(
1207                            if !has_tags
1208                                || expr
1209                                    .node
1210                                    .metadata
1211                                    .tags
1212                                    .iter()
1213                                    .any(|candidate| tags.contains(&candidate.as_ref()))
1214                            {
1215                                expr.clone()
1216                            } else {
1217                                expr.zero_like()
1218                            },
1219                        );
1220                        continue;
1221                    }
1222                    let child_count = expr.node.kind.child_count();
1223                    stack.push(Frame::Rebuild(expr.clone(), child_count));
1224                    for index in (0..child_count).rev() {
1225                        stack.push(Frame::Visit(expr.node.kind.child_at(index).clone()));
1226                    }
1227                }
1228                Frame::Rebuild(expr, child_count) => {
1229                    let child_start = projected.len() - child_count;
1230                    let children = projected.split_off(child_start);
1231                    let mut children = children.into_iter();
1232                    let kind = expr.node.kind.map_children(|original| {
1233                        children.next().unwrap_or_else(|| original.clone())
1234                    });
1235                    projected.push(
1236                        Expr::new(kind)
1237                            .with_metadata(|metadata| *metadata = expr.node.metadata.clone()),
1238                    );
1239                }
1240            }
1241        }
1242        projected.pop().unwrap_or_else(|| self.clone())
1243    }
1244
1245    fn zero_like(&self) -> Self {
1246        match self
1247            .shape()
1248            .expect("valid expression shapes are cached eagerly")
1249        {
1250            ExprShape::Scalar => Expr::from(0.0),
1251            ExprShape::Vector { len } => vector((0..len).map(|_| Expr::from(0.0))),
1252            ExprShape::Matrix { rows, cols } => {
1253                matrix_from_flat(rows, cols, (0..rows * cols).map(|_| Expr::from(0.0)))
1254                    .expect("zero matrix dimensions match")
1255            }
1256        }
1257    }
1258
1259    /// Returns an expression for the real part.
1260    pub fn real(&self) -> Self {
1261        unary(UnaryOp::Real, self)
1262    }
1263
1264    /// Returns an expression for the imaginary part.
1265    pub fn imag(&self) -> Self {
1266        unary(UnaryOp::Imag, self)
1267    }
1268
1269    /// Returns an expression for the complex conjugate.
1270    pub fn conj(&self) -> Self {
1271        unary(UnaryOp::Conj, self)
1272    }
1273
1274    /// Returns an expression for the squared complex norm.
1275    pub fn norm_sqr(&self) -> Self {
1276        unary(UnaryOp::NormSqr, self)
1277    }
1278
1279    /// Returns an expression for the principal square root.
1280    pub fn sqrt(&self) -> Self {
1281        unary(UnaryOp::Sqrt, self)
1282    }
1283
1284    /// Returns an expression for the exponential.
1285    pub fn exp(&self) -> Self {
1286        unary(UnaryOp::Exp, self)
1287    }
1288
1289    /// Returns an expression for the sine.
1290    pub fn sin(&self) -> Self {
1291        unary(UnaryOp::Sin, self)
1292    }
1293
1294    /// Returns an expression for the cosine.
1295    pub fn cos(&self) -> Self {
1296        unary(UnaryOp::Cos, self)
1297    }
1298
1299    /// Returns an expression for the principal arccosine.
1300    pub fn acos(&self) -> Self {
1301        atan2((Expr::from(1.0) - self.powi(2)).sqrt(), self)
1302    }
1303
1304    /// Returns an expression for the natural logarithm.
1305    pub fn log(&self) -> Self {
1306        unary(UnaryOp::Log, self)
1307    }
1308
1309    /// Returns an expression raised to an integer power.
1310    pub fn powi(&self, power: i32) -> Self {
1311        unary(UnaryOp::PowI(power), self)
1312    }
1313
1314    /// Selects a component from a vector-valued expression.
1315    pub fn component(&self, index: impl ComponentIndex) -> Self {
1316        Expr::new(DagNodeKind::Component {
1317            input: self.clone(),
1318            index: index.component_index(),
1319        })
1320    }
1321
1322    /// Selects an element from a matrix-valued expression.
1323    pub fn matrix_element(&self, row: usize, col: usize) -> Self {
1324        Expr::new(DagNodeKind::MatrixElement {
1325            input: self.clone(),
1326            row,
1327            col,
1328        })
1329    }
1330
1331    /// Serializes the shareable expression DAG into a topologically ordered graph.
1332    pub fn to_graph(&self) -> ExprGraph {
1333        GraphBuilder::new().build(self)
1334    }
1335
1336    /// Rebuilds a shareable expression DAG from its serialized graph form.
1337    ///
1338    /// # Errors
1339    ///
1340    /// Returns [`ExprGraphError`] when the graph is empty, its root or a child
1341    /// identifier is invalid, its metadata length does not match its node
1342    /// count, or its nodes are not topologically ordered.
1343    pub fn from_graph(graph: ExprGraph) -> Result<Self, ExprGraphError> {
1344        let ExprGraph {
1345            root,
1346            nodes,
1347            metadata,
1348        } = graph;
1349        let graph = ExprGraph::from_parts(root, nodes, metadata)?;
1350        let mut expressions: Vec<Expr> = Vec::with_capacity(graph.nodes.len());
1351        for (index, node) in graph.nodes.iter().enumerate() {
1352            let child = |id: ExprId| expressions[id.index()].clone();
1353            let expression = match node {
1354                ExprNode::RealConst(value) => Expr::new(DagNodeKind::RealConst(*value)),
1355                ExprNode::ComplexConst(value) => Expr::new(DagNodeKind::ComplexConst(*value)),
1356                ExprNode::ScalarParam(parameter) => {
1357                    Expr::new(DagNodeKind::ScalarParam(parameter.clone()))
1358                }
1359                ExprNode::EventScalar(name) => {
1360                    Expr::new(DagNodeKind::EventScalar(Arc::clone(name)))
1361                }
1362                ExprNode::EventP4Component { name, component } => {
1363                    Expr::new(DagNodeKind::EventP4Component {
1364                        name: Arc::clone(name),
1365                        component: *component,
1366                    })
1367                }
1368                ExprNode::Unary { op, input } => Expr::new(DagNodeKind::Unary {
1369                    op: *op,
1370                    input: child(*input),
1371                }),
1372                ExprNode::Binary { op, lhs, rhs } => Expr::new(DagNodeKind::Binary {
1373                    op: *op,
1374                    lhs: child(*lhs),
1375                    rhs: child(*rhs),
1376                }),
1377                ExprNode::NaryAdd { terms } => terms
1378                    .iter()
1379                    .map(|id| child(*id))
1380                    .reduce(|lhs, rhs| binary(BinaryOp::Add, &lhs, &rhs))
1381                    .unwrap_or_else(|| Expr::from(0.0)),
1382                ExprNode::NaryMul { factors } => factors
1383                    .iter()
1384                    .map(|id| child(*id))
1385                    .reduce(|lhs, rhs| binary(BinaryOp::Mul, &lhs, &rhs))
1386                    .unwrap_or_else(|| Expr::from(1.0)),
1387                ExprNode::Complex { re, im } => Expr::new(DagNodeKind::Complex {
1388                    re: child(*re),
1389                    im: child(*im),
1390                }),
1391                ExprNode::Vector { elements } => Expr::new(DagNodeKind::Vector {
1392                    elements: elements.iter().map(|id| child(*id)).collect(),
1393                }),
1394                ExprNode::Matrix {
1395                    rows,
1396                    cols,
1397                    elements,
1398                } => Expr::new(DagNodeKind::Matrix {
1399                    rows: *rows,
1400                    cols: *cols,
1401                    elements: elements.iter().map(|id| child(*id)).collect(),
1402                }),
1403                ExprNode::Component { input, index } => Expr::new(DagNodeKind::Component {
1404                    input: child(*input),
1405                    index: *index,
1406                }),
1407                ExprNode::MatrixElement { input, row, col } => {
1408                    Expr::new(DagNodeKind::MatrixElement {
1409                        input: child(*input),
1410                        row: *row,
1411                        col: *col,
1412                    })
1413                }
1414                ExprNode::MatMul { lhs, rhs } => Expr::new(DagNodeKind::MatMul {
1415                    lhs: child(*lhs),
1416                    rhs: child(*rhs),
1417                }),
1418                ExprNode::MatVec { matrix, vector } => Expr::new(DagNodeKind::MatVec {
1419                    matrix: child(*matrix),
1420                    vector: child(*vector),
1421                }),
1422                ExprNode::Dot { lhs, rhs } => Expr::new(DagNodeKind::Dot {
1423                    lhs: child(*lhs),
1424                    rhs: child(*rhs),
1425                }),
1426                ExprNode::Solve { matrix, rhs } => Expr::new(DagNodeKind::Solve {
1427                    matrix: child(*matrix),
1428                    rhs: child(*rhs),
1429                }),
1430            };
1431            let mut dag = (*expression.node).clone();
1432            dag.metadata = graph.metadata[index].clone();
1433            expressions.push(Expr {
1434                node: Arc::new(dag),
1435            });
1436        }
1437        Ok(expressions[graph.root.index()].clone())
1438    }
1439
1440    /// Determines and validates the expression's structural shape.
1441    ///
1442    /// # Errors
1443    ///
1444    /// Returns [`ExprShapeError`] when this expression contains an operation
1445    /// whose operand shapes are incompatible.
1446    pub fn shape(&self) -> Result<ExprShape, ExprShapeError> {
1447        self.node
1448            .shape
1449            .get_or_init(|| self.node.kind.shape())
1450            .clone()
1451    }
1452
1453    fn with_metadata(self, f: impl FnOnce(&mut ExprMetadata)) -> Self {
1454        let mut node = (*self.node).clone();
1455        f(&mut node.metadata);
1456        Self {
1457            node: Arc::new(node),
1458        }
1459    }
1460}
1461
1462impl Serialize for Expr {
1463    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1464    where
1465        S: serde::Serializer,
1466    {
1467        self.to_graph().serialize(serializer)
1468    }
1469}
1470
1471impl<'de> Deserialize<'de> for Expr {
1472    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1473    where
1474        D: serde::Deserializer<'de>,
1475    {
1476        Expr::from_graph(ExprGraph::deserialize(deserializer)?).map_err(serde::de::Error::custom)
1477    }
1478}
1479
1480impl DagNodeKind {
1481    fn shape(&self) -> Result<ExprShape, ExprShapeError> {
1482        match self {
1483            Self::RealConst(_)
1484            | Self::ComplexConst(_)
1485            | Self::ScalarParam(_)
1486            | Self::EventScalar(_)
1487            | Self::EventP4Component { .. } => Ok(ExprShape::Scalar),
1488            Self::Unary { input, .. } => {
1489                input.expect_shape("unary operation", ExprShape::Scalar)?;
1490                Ok(ExprShape::Scalar)
1491            }
1492            Self::Binary { lhs, rhs, .. } => {
1493                lhs.expect_shape("binary operation", ExprShape::Scalar)?;
1494                rhs.expect_shape("binary operation", ExprShape::Scalar)?;
1495                Ok(ExprShape::Scalar)
1496            }
1497            Self::Complex { re, im } => {
1498                re.expect_shape("complex constructor", ExprShape::Scalar)?;
1499                im.expect_shape("complex constructor", ExprShape::Scalar)?;
1500                Ok(ExprShape::Scalar)
1501            }
1502            Self::Vector { elements } => {
1503                for element in elements {
1504                    element.expect_shape("vector constructor", ExprShape::Scalar)?;
1505                }
1506                Ok(ExprShape::Vector {
1507                    len: elements.len(),
1508                })
1509            }
1510            Self::Matrix {
1511                rows,
1512                cols,
1513                elements,
1514            } => {
1515                let expected = rows.checked_mul(*cols).ok_or_else(|| {
1516                    ExprShapeError::new("matrix constructor", "row/column product overflowed")
1517                })?;
1518                if elements.len() != expected {
1519                    return Err(ExprShapeError::new(
1520                        "matrix constructor",
1521                        format!(
1522                            "shape {rows}x{cols} requires {expected} elements, got {}",
1523                            elements.len()
1524                        ),
1525                    ));
1526                }
1527                for element in elements {
1528                    element.expect_shape("matrix constructor", ExprShape::Scalar)?;
1529                }
1530                Ok(ExprShape::Matrix {
1531                    rows: *rows,
1532                    cols: *cols,
1533                })
1534            }
1535            Self::Component { input, index } => {
1536                let ExprShape::Vector { len } = input.shape()? else {
1537                    return Err(ExprShapeError::new(
1538                        "component",
1539                        format!("expected vector, got {}", input.shape()?),
1540                    ));
1541                };
1542                if *index >= len {
1543                    return Err(ExprShapeError::new(
1544                        "component",
1545                        format!("index {index} is out of bounds for vector[{len}]"),
1546                    ));
1547                }
1548                Ok(ExprShape::Scalar)
1549            }
1550            Self::MatrixElement { input, row, col } => {
1551                let ExprShape::Matrix { rows, cols } = input.shape()? else {
1552                    return Err(ExprShapeError::new(
1553                        "matrix element",
1554                        format!("expected matrix, got {}", input.shape()?),
1555                    ));
1556                };
1557                if *row >= rows || *col >= cols {
1558                    return Err(ExprShapeError::new(
1559                        "matrix element",
1560                        format!("index ({row}, {col}) is out of bounds for matrix[{rows}x{cols}]"),
1561                    ));
1562                }
1563                Ok(ExprShape::Scalar)
1564            }
1565            Self::MatMul { lhs, rhs } => {
1566                let ExprShape::Matrix {
1567                    rows: lhs_rows,
1568                    cols: lhs_cols,
1569                } = lhs.shape()?
1570                else {
1571                    return Err(ExprShapeError::new(
1572                        "matrix multiplication",
1573                        format!("left input must be a matrix, got {}", lhs.shape()?),
1574                    ));
1575                };
1576                let ExprShape::Matrix {
1577                    rows: rhs_rows,
1578                    cols: rhs_cols,
1579                } = rhs.shape()?
1580                else {
1581                    return Err(ExprShapeError::new(
1582                        "matrix multiplication",
1583                        format!("right input must be a matrix, got {}", rhs.shape()?),
1584                    ));
1585                };
1586                if lhs_cols != rhs_rows {
1587                    return Err(ExprShapeError::new(
1588                        "matrix multiplication",
1589                        format!("cannot multiply {lhs_rows}x{lhs_cols} by {rhs_rows}x{rhs_cols}"),
1590                    ));
1591                }
1592                Ok(ExprShape::Matrix {
1593                    rows: lhs_rows,
1594                    cols: rhs_cols,
1595                })
1596            }
1597            Self::MatVec { matrix, vector } => {
1598                let ExprShape::Matrix { rows, cols } = matrix.shape()? else {
1599                    return Err(ExprShapeError::new(
1600                        "matrix-vector multiplication",
1601                        format!("left input must be a matrix, got {}", matrix.shape()?),
1602                    ));
1603                };
1604                let ExprShape::Vector { len } = vector.shape()? else {
1605                    return Err(ExprShapeError::new(
1606                        "matrix-vector multiplication",
1607                        format!("right input must be a vector, got {}", vector.shape()?),
1608                    ));
1609                };
1610                if cols != len {
1611                    return Err(ExprShapeError::new(
1612                        "matrix-vector multiplication",
1613                        format!("cannot multiply {rows}x{cols} matrix by vector[{len}]"),
1614                    ));
1615                }
1616                Ok(ExprShape::Vector { len: rows })
1617            }
1618            Self::Dot { lhs, rhs } => {
1619                let ExprShape::Vector { len: lhs_len } = lhs.shape()? else {
1620                    return Err(ExprShapeError::new(
1621                        "dot product",
1622                        format!("left input must be a vector, got {}", lhs.shape()?),
1623                    ));
1624                };
1625                let ExprShape::Vector { len: rhs_len } = rhs.shape()? else {
1626                    return Err(ExprShapeError::new(
1627                        "dot product",
1628                        format!("right input must be a vector, got {}", rhs.shape()?),
1629                    ));
1630                };
1631                if lhs_len != rhs_len {
1632                    return Err(ExprShapeError::new(
1633                        "dot product",
1634                        format!("vector lengths differ: {lhs_len} and {rhs_len}"),
1635                    ));
1636                }
1637                Ok(ExprShape::Scalar)
1638            }
1639            Self::Solve { matrix, rhs } => {
1640                let ExprShape::Matrix { rows, cols } = matrix.shape()? else {
1641                    return Err(ExprShapeError::new(
1642                        "linear solve",
1643                        format!("left input must be a matrix, got {}", matrix.shape()?),
1644                    ));
1645                };
1646                let ExprShape::Vector { len } = rhs.shape()? else {
1647                    return Err(ExprShapeError::new(
1648                        "linear solve",
1649                        format!("right input must be a vector, got {}", rhs.shape()?),
1650                    ));
1651                };
1652                if rows != cols || rows != len {
1653                    return Err(ExprShapeError::new(
1654                        "linear solve",
1655                        format!("cannot solve matrix[{rows}x{cols}] against vector[{len}]"),
1656                    ));
1657                }
1658                Ok(ExprShape::Vector { len })
1659            }
1660        }
1661    }
1662}
1663
1664impl Expr {
1665    fn expect_shape(
1666        &self,
1667        operation: &'static str,
1668        expected: ExprShape,
1669    ) -> Result<(), ExprShapeError> {
1670        let actual = self.shape()?;
1671        if actual != expected {
1672            return Err(ExprShapeError::new(
1673                operation,
1674                format!("expected {expected}, got {actual}"),
1675            ));
1676        }
1677        Ok(())
1678    }
1679}
1680
1681auto_ops::impl_op_ex!(+ |a: &Expr, b: &Expr| -> Expr { binary(BinaryOp::Add, a, b) });
1682
1683auto_ops::impl_op_ex!(+ |a: &Expr, b: &f64| -> Expr { binary(BinaryOp::Add, a, b) });
1684auto_ops::impl_op_ex!(+ |a: &f64, b: &Expr| -> Expr { binary(BinaryOp::Add, a, b) });
1685
1686auto_ops::impl_op_ex!(+ |a: &Expr, b: &Complex64| -> Expr { binary(BinaryOp::Add, a, b) });
1687auto_ops::impl_op_ex!(+ |a: &Complex64, b: &Expr| -> Expr { binary(BinaryOp::Add, a, b) });
1688
1689auto_ops::impl_op_ex!(+ |a: &Expr, b: &Parameter| -> Expr { binary(BinaryOp::Add, a, b) });
1690auto_ops::impl_op_ex!(+ |a: &Parameter, b: &Expr| -> Expr { binary(BinaryOp::Add, a, b) });
1691
1692auto_ops::impl_op_ex!(+ |a: &Parameter, b: &f64| -> Expr { binary(BinaryOp::Add, a, b) });
1693auto_ops::impl_op_ex!(+ |a: &f64, b: &Parameter| -> Expr { binary(BinaryOp::Add, a, b) });
1694
1695auto_ops::impl_op_ex!(+ |a: &Parameter, b: &Complex64| -> Expr { binary(BinaryOp::Add, a, b) });
1696auto_ops::impl_op_ex!(+ |a: &Complex64, b: &Parameter| -> Expr { binary(BinaryOp::Add, a, b) });
1697
1698auto_ops::impl_op_ex!(+ |a: &Parameter, b: &Parameter| -> Expr { binary(BinaryOp::Add, a, b) });
1699
1700auto_ops::impl_op_ex!(-|a: &Expr, b: &Expr| -> Expr { binary(BinaryOp::Sub, a, b) });
1701
1702auto_ops::impl_op_ex!(-|a: &Expr, b: &f64| -> Expr { binary(BinaryOp::Sub, a, b) });
1703
1704auto_ops::impl_op_ex!(-|a: &f64, b: &Expr| -> Expr { binary(BinaryOp::Sub, a, b) });
1705
1706auto_ops::impl_op_ex!(-|a: &Expr, b: &Complex64| -> Expr { binary(BinaryOp::Sub, a, b) });
1707
1708auto_ops::impl_op_ex!(-|a: &Complex64, b: &Expr| -> Expr { binary(BinaryOp::Sub, a, b) });
1709
1710auto_ops::impl_op_ex!(-|a: &Expr, b: &Parameter| -> Expr { binary(BinaryOp::Sub, a, b) });
1711
1712auto_ops::impl_op_ex!(-|a: &Parameter, b: &Expr| -> Expr { binary(BinaryOp::Sub, a, b) });
1713
1714auto_ops::impl_op_ex!(-|a: &f64, b: &Parameter| -> Expr { binary(BinaryOp::Sub, a, b) });
1715
1716auto_ops::impl_op_ex!(-|a: &Parameter, b: &f64| -> Expr { binary(BinaryOp::Sub, a, b) });
1717
1718auto_ops::impl_op_ex!(-|a: &Complex64, b: &Parameter| -> Expr { binary(BinaryOp::Sub, a, b) });
1719
1720auto_ops::impl_op_ex!(-|a: &Parameter, b: &Complex64| -> Expr { binary(BinaryOp::Sub, a, b) });
1721
1722auto_ops::impl_op_ex!(-|a: &Parameter, b: &Parameter| -> Expr { binary(BinaryOp::Sub, a, b) });
1723
1724auto_ops::impl_op_ex!(*|a: &Expr, b: &Expr| -> Expr { binary(BinaryOp::Mul, a, b) });
1725
1726auto_ops::impl_op_ex!(*|a: &Expr, b: &f64| -> Expr { binary(BinaryOp::Mul, a, b) });
1727auto_ops::impl_op_ex!(*|a: &f64, b: &Expr| -> Expr { binary(BinaryOp::Mul, a, b) });
1728
1729auto_ops::impl_op_ex!(*|a: &Expr, b: &Complex64| -> Expr { binary(BinaryOp::Mul, a, b) });
1730auto_ops::impl_op_ex!(*|a: &Complex64, b: &Expr| -> Expr { binary(BinaryOp::Mul, a, b) });
1731
1732auto_ops::impl_op_ex!(*|a: &Expr, b: &Parameter| -> Expr { binary(BinaryOp::Mul, a, b) });
1733auto_ops::impl_op_ex!(*|a: &Parameter, b: &Expr| -> Expr { binary(BinaryOp::Mul, a, b) });
1734
1735auto_ops::impl_op_ex!(*|a: &f64, b: &Parameter| -> Expr { binary(BinaryOp::Mul, a, b) });
1736auto_ops::impl_op_ex!(*|a: &Parameter, b: &f64| -> Expr { binary(BinaryOp::Mul, a, b) });
1737
1738auto_ops::impl_op_ex!(*|a: &Complex64, b: &Parameter| -> Expr { binary(BinaryOp::Mul, a, b) });
1739auto_ops::impl_op_ex!(*|a: &Parameter, b: &Complex64| -> Expr { binary(BinaryOp::Mul, a, b) });
1740
1741auto_ops::impl_op_ex!(*|a: &Parameter, b: &Parameter| -> Expr { binary(BinaryOp::Mul, a, b) });
1742
1743auto_ops::impl_op_ex!(/ |a: &Expr, b: &Expr| -> Expr {
1744    binary(BinaryOp::Div, a, b)
1745});
1746
1747auto_ops::impl_op_ex!(/ |a: &Expr, b: &Complex64| -> Expr { binary(BinaryOp::Div, a, b) });
1748auto_ops::impl_op_ex!(/ |a: &Complex64, b: &Expr| -> Expr { binary(BinaryOp::Div, a, b) });
1749
1750auto_ops::impl_op_ex!(/ |a: &Expr, b: &f64| -> Expr { binary(BinaryOp::Div, a, b) });
1751auto_ops::impl_op_ex!(/ |a: &f64, b: &Expr| -> Expr { binary(BinaryOp::Div, a, b) });
1752
1753auto_ops::impl_op_ex!(/|a: &Expr, b: &Parameter| -> Expr {
1754    binary(BinaryOp::Div, a, b)
1755});
1756auto_ops::impl_op_ex!(/|a: &Parameter, b: &Expr| -> Expr {
1757    binary(BinaryOp::Div, a, b)
1758});
1759
1760auto_ops::impl_op_ex!(/|a: &f64, b: &Parameter| -> Expr {
1761    binary(BinaryOp::Div, a, b)
1762});
1763auto_ops::impl_op_ex!(/|a: &Parameter, b: &f64| -> Expr {
1764    binary(BinaryOp::Div, a, b)
1765});
1766
1767auto_ops::impl_op_ex!(/|a: &Complex64, b: &Parameter| -> Expr {
1768    binary(BinaryOp::Div, a, b)
1769});
1770auto_ops::impl_op_ex!(/|a: &Parameter, b: &Complex64| -> Expr {
1771    binary(BinaryOp::Div, a, b)
1772});
1773
1774auto_ops::impl_op_ex!(/|a: &Parameter, b: &Parameter| -> Expr {
1775    binary(BinaryOp::Div, a, b)
1776});
1777
1778auto_ops::impl_op_ex!(-|a: &Expr| -> Expr { unary(UnaryOp::Neg, a) });
1779auto_ops::impl_op_ex!(-|a: &Parameter| -> Expr { unary(UnaryOp::Neg, a) });
1780
1781auto_ops::impl_op_ex!(+= |a: &mut Expr, b: &Expr| {
1782    *a = binary(BinaryOp::Add, &*a, b);
1783});
1784auto_ops::impl_op_ex!(+= |a: &mut Expr, b: &f64| {
1785    *a = binary(BinaryOp::Add, &*a, b);
1786});
1787auto_ops::impl_op_ex!(+= |a: &mut Expr, b: &Complex64| {
1788    *a = binary(BinaryOp::Add, &*a, b);
1789});
1790auto_ops::impl_op_ex!(+= |a: &mut Expr, b: &Parameter| {
1791    *a = binary(BinaryOp::Add, &*a, b);
1792});
1793
1794auto_ops::impl_op_ex!(-= |a: &mut Expr, b: &Expr| {
1795    *a = binary(BinaryOp::Sub, &*a, b);
1796});
1797auto_ops::impl_op_ex!(-= |a: &mut Expr, b: &f64| {
1798    *a = binary(BinaryOp::Sub, &*a, b);
1799});
1800auto_ops::impl_op_ex!(-= |a: &mut Expr, b: &Complex64| {
1801    *a = binary(BinaryOp::Sub, &*a, b);
1802});
1803auto_ops::impl_op_ex!(-= |a: &mut Expr, b: &Parameter| {
1804    *a = binary(BinaryOp::Sub, &*a, b);
1805});
1806
1807auto_ops::impl_op_ex!(*= |a: &mut Expr, b: &Expr| {
1808    *a = binary(BinaryOp::Mul, &*a, b);
1809});
1810auto_ops::impl_op_ex!(*= |a: &mut Expr, b: &f64| {
1811    *a = binary(BinaryOp::Mul, &*a, b);
1812});
1813auto_ops::impl_op_ex!(*= |a: &mut Expr, b: &Complex64| {
1814    *a = binary(BinaryOp::Mul, &*a, b);
1815});
1816auto_ops::impl_op_ex!(*= |a: &mut Expr, b: &Parameter| {
1817    *a = binary(BinaryOp::Mul, &*a, b);
1818});
1819
1820auto_ops::impl_op_ex!(/= |a: &mut Expr, b: &Expr| {
1821    *a = binary(BinaryOp::Div, &*a, b);
1822});
1823auto_ops::impl_op_ex!(/= |a: &mut Expr, b: &f64| {
1824    *a = binary(BinaryOp::Div, &*a, b);
1825});
1826auto_ops::impl_op_ex!(/= |a: &mut Expr, b: &Complex64| {
1827    *a = binary(BinaryOp::Div, &*a, b);
1828});
1829auto_ops::impl_op_ex!(/= |a: &mut Expr, b: &Parameter| {
1830    *a = binary(BinaryOp::Div, &*a, b);
1831});
1832
1833impl From<f64> for Expr {
1834    fn from(value: f64) -> Self {
1835        Self::new(DagNodeKind::RealConst(value))
1836    }
1837}
1838
1839impl From<&f64> for Expr {
1840    fn from(value: &f64) -> Self {
1841        Self::new(DagNodeKind::RealConst(*value))
1842    }
1843}
1844
1845impl From<Complex64> for Expr {
1846    fn from(value: Complex64) -> Self {
1847        Self::new(DagNodeKind::ComplexConst(value))
1848    }
1849}
1850
1851impl From<&Complex64> for Expr {
1852    fn from(value: &Complex64) -> Self {
1853        Self::new(DagNodeKind::ComplexConst(*value))
1854    }
1855}
1856
1857impl From<&Expr> for Expr {
1858    fn from(value: &Expr) -> Self {
1859        value.clone()
1860    }
1861}
1862
1863impl From<Parameter> for Expr {
1864    fn from(parameter: Parameter) -> Self {
1865        Expr::new(DagNodeKind::ScalarParam(parameter))
1866    }
1867}
1868
1869impl From<&Parameter> for Expr {
1870    fn from(parameter: &Parameter) -> Self {
1871        parameter.clone().into()
1872    }
1873}
1874
1875/// Constructs `cos(phase) + i sin(phase)`.
1876pub fn cis(phase: Expr) -> Expr {
1877    phase.cos() + Complex64::I * phase.sin()
1878}
1879
1880/// Constructs a complex scalar from real and imaginary expressions.
1881pub fn complex(re: impl Into<Expr>, im: impl Into<Expr>) -> Expr {
1882    Expr::new(DagNodeKind::Complex {
1883        re: re.into(),
1884        im: im.into(),
1885    })
1886}
1887
1888/// Constructs a complex scalar from magnitude and phase expressions.
1889pub fn polar_complex(mag: impl Into<Expr>, phase: impl Into<Expr>) -> Expr {
1890    mag.into() * (Complex64::I * phase.into()).exp()
1891}
1892
1893/// References a named scalar column in each event.
1894pub fn event_scalar(name: impl Into<Arc<str>>) -> Expr {
1895    Expr::new(DagNodeKind::EventScalar(name.into()))
1896}
1897
1898/// References one component of a named event four-momentum.
1899pub fn event_p4_component(name: impl Into<Arc<str>>, component: P4Component) -> Expr {
1900    Expr::new(DagNodeKind::EventP4Component {
1901        name: name.into(),
1902        component,
1903    })
1904}
1905
1906/// Constructs the two-argument arctangent `atan2(y, x)`.
1907pub fn atan2(y: impl Into<Expr>, x: impl Into<Expr>) -> Expr {
1908    binary(BinaryOp::Atan2, y, x)
1909}
1910
1911/// Constructs the principal arccosine of `value`.
1912pub fn acos(value: impl Into<Expr>) -> Expr {
1913    value.into().acos()
1914}
1915
1916/// Constructs a vector expression from scalar elements.
1917pub fn vector<E>(elements: impl IntoIterator<Item = E>) -> Expr
1918where
1919    E: Into<Expr>,
1920    Expr: From<E>,
1921{
1922    Expr::new(DagNodeKind::Vector {
1923        elements: elements.into_iter().map(Expr::from).collect(),
1924    })
1925}
1926
1927/// Constructs a row-major matrix expression from a nested array.
1928pub fn matrix<const R: usize, const C: usize, E>(elements: [[E; C]; R]) -> Expr
1929where
1930    E: Into<Expr>,
1931    Expr: From<E>,
1932{
1933    Expr::new(DagNodeKind::Matrix {
1934        rows: R,
1935        cols: C,
1936        elements: elements.into_iter().flatten().map(Expr::from).collect(),
1937    })
1938}
1939
1940/// Constructs a row-major matrix from a flat sequence.
1941///
1942/// # Errors
1943///
1944/// Returns [`ExprShapeError`] when either dimension is zero, the dimension
1945/// product overflows, the element count differs from `rows * cols`, or an
1946/// element is not scalar-valued.
1947pub fn matrix_from_flat<E>(
1948    rows: usize,
1949    cols: usize,
1950    elements: impl IntoIterator<Item = E>,
1951) -> Result<Expr, ExprShapeError>
1952where
1953    E: Into<Expr>,
1954    Expr: From<E>,
1955{
1956    if rows == 0 || cols == 0 {
1957        return Err(ExprShapeError::new(
1958            "matrix constructor",
1959            format!("matrix dimensions must be nonzero, got {rows}x{cols}"),
1960        ));
1961    }
1962    let expected = rows.checked_mul(cols).ok_or_else(|| {
1963        ExprShapeError::new("matrix constructor", "row/column product overflowed")
1964    })?;
1965    let elements = elements.into_iter().map(Expr::from).collect::<Vec<_>>();
1966    if elements.len() != expected {
1967        return Err(ExprShapeError::new(
1968            "matrix constructor",
1969            format!(
1970                "shape {rows}x{cols} requires {expected} elements, got {}",
1971                elements.len()
1972            ),
1973        ));
1974    }
1975    for element in &elements {
1976        element.expect_shape("matrix constructor", ExprShape::Scalar)?;
1977    }
1978    Ok(Expr::new(DagNodeKind::Matrix {
1979        rows,
1980        cols,
1981        elements,
1982    }))
1983}
1984
1985/// Constructs a matrix-matrix multiplication expression.
1986pub fn matmul(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Expr {
1987    Expr::new(DagNodeKind::MatMul {
1988        lhs: lhs.into(),
1989        rhs: rhs.into(),
1990    })
1991}
1992
1993/// Constructs a matrix-vector multiplication expression.
1994pub fn matvec(matrix: impl Into<Expr>, vector: impl Into<Expr>) -> Expr {
1995    Expr::new(DagNodeKind::MatVec {
1996        matrix: matrix.into(),
1997        vector: vector.into(),
1998    })
1999}
2000
2001/// Constructs a vector dot-product expression.
2002pub fn dot(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Expr {
2003    Expr::new(DagNodeKind::Dot {
2004        lhs: lhs.into(),
2005        rhs: rhs.into(),
2006    })
2007}
2008
2009/// Constructs an expression that solves a linear system.
2010pub fn solve(matrix: impl Into<Expr>, rhs: impl Into<Expr>) -> Expr {
2011    Expr::new(DagNodeKind::Solve {
2012        matrix: matrix.into(),
2013        rhs: rhs.into(),
2014    })
2015}
2016
2017fn unary(op: UnaryOp, expr: impl Into<Expr>) -> Expr {
2018    Expr::new(DagNodeKind::Unary {
2019        op,
2020        input: expr.into(),
2021    })
2022}
2023
2024fn binary(op: BinaryOp, lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Expr {
2025    Expr::new(DagNodeKind::Binary {
2026        op,
2027        lhs: lhs.into(),
2028        rhs: rhs.into(),
2029    })
2030}
2031
2032/// Topologically ordered, serializable representation of an [`Expr`] DAG.
2033#[derive(Clone, Debug, Serialize, Deserialize)]
2034pub struct ExprGraph {
2035    root: ExprId,
2036    nodes: Vec<ExprNode>,
2037    metadata: Vec<ExprMetadata>,
2038}
2039
2040/// Workspace-internal accumulator for rebuilding expression graphs.
2041///
2042/// Nodes and metadata are emitted together in child-before-parent order. The
2043/// caller owns rewrite policy and chooses the remap key, which may include
2044/// traversal context in addition to the source node identifier.
2045#[doc(hidden)]
2046pub struct ExprGraphRebuilder<K> {
2047    nodes: Vec<ExprNode>,
2048    metadata: Vec<ExprMetadata>,
2049    remapped: HashMap<K, ExprId>,
2050}
2051
2052#[doc(hidden)]
2053impl<K> ExprGraphRebuilder<K>
2054where
2055    K: Eq + Hash,
2056{
2057    /// Creates an empty rebuild accumulator sized for the expected output.
2058    pub fn with_capacity(capacity: usize) -> Self {
2059        Self {
2060            nodes: Vec::with_capacity(capacity),
2061            metadata: Vec::with_capacity(capacity),
2062            remapped: HashMap::with_capacity(capacity),
2063        }
2064    }
2065
2066    /// Returns the emitted identifier associated with `key`, if any.
2067    pub fn remapped(&self, key: &K) -> Option<ExprId> {
2068        self.remapped.get(key).copied()
2069    }
2070
2071    /// Returns the nodes emitted so far in child-before-parent order.
2072    pub fn nodes(&self) -> &[ExprNode] {
2073        &self.nodes
2074    }
2075
2076    /// Returns the metadata emitted so far, aligned with [`Self::nodes`].
2077    pub fn metadata(&self) -> &[ExprMetadata] {
2078        &self.metadata
2079    }
2080
2081    /// Associates `key` with an already emitted node.
2082    ///
2083    /// This supports graph transforms that remove a source node by aliasing it
2084    /// to an existing result.
2085    ///
2086    /// # Panics
2087    ///
2088    /// Panics if `key` was mapped previously or `id` has not been emitted.
2089    pub fn alias(&mut self, key: K, id: ExprId) {
2090        assert!(
2091            !self.remapped.contains_key(&key),
2092            "a rebuild key may only be mapped once"
2093        );
2094        assert!(
2095            id.index() < self.nodes.len(),
2096            "a rebuild alias must reference an emitted node"
2097        );
2098        self.remapped.insert(key, id);
2099    }
2100
2101    /// Emits one node and its aligned metadata without assigning a remap key.
2102    ///
2103    /// This supports replacement fragments whose intermediate nodes do not
2104    /// correspond one-to-one with source nodes.
2105    ///
2106    /// # Panics
2107    ///
2108    /// Panics if the node references a child that has not already been emitted.
2109    pub fn emit_anonymous(&mut self, node: ExprNode, metadata: ExprMetadata) -> ExprId {
2110        let id = ExprId::from_index(self.nodes.len());
2111        assert!(
2112            node.children().all(|child| child.index() < id.index()),
2113            "rebuilt expression children must be emitted before their parent"
2114        );
2115        self.nodes.push(node);
2116        self.metadata.push(metadata);
2117        id
2118    }
2119
2120    /// Emits one node and its aligned metadata after all of its children.
2121    ///
2122    /// # Panics
2123    ///
2124    /// Panics if `key` was mapped previously or if the node references a
2125    /// child that has not already been emitted.
2126    pub fn emit(&mut self, key: K, node: ExprNode, metadata: ExprMetadata) -> ExprId {
2127        assert!(
2128            !self.remapped.contains_key(&key),
2129            "a rebuild key may only be mapped once"
2130        );
2131        let id = self.emit_anonymous(node, metadata);
2132        self.remapped.insert(key, id);
2133        id
2134    }
2135
2136    /// Validates and finishes the rebuilt graph with `root` as its root node.
2137    pub fn finish(self, root: ExprId) -> Result<ExprGraph, ExprGraphError> {
2138        ExprGraph::from_parts(root, self.nodes, self.metadata)
2139    }
2140}
2141
2142impl ExprGraph {
2143    /// Returns the nodes reachable from `roots` in child-before-parent order.
2144    ///
2145    /// The traversal is iterative, visits each node at most once, and preserves
2146    /// semantic child order. This is a workspace-internal traversal primitive
2147    /// for graph consumers that must remain safe for deeply nested graphs.
2148    #[doc(hidden)]
2149    pub fn reachable_post_order(&self, roots: impl IntoIterator<Item = ExprId>) -> Vec<ExprId> {
2150        let roots = roots.into_iter().collect::<Vec<_>>();
2151        let mut visited = HashSet::with_capacity(self.nodes.len());
2152        let mut stack = Vec::new();
2153        let mut order = Vec::new();
2154
2155        for root in roots.into_iter().rev() {
2156            stack.push((root, false));
2157        }
2158        while let Some((id, expanded)) = stack.pop() {
2159            if expanded {
2160                order.push(id);
2161                continue;
2162            }
2163            if self.node(id).is_none() {
2164                continue;
2165            }
2166            if !visited.insert(id) {
2167                continue;
2168            }
2169            stack.push((id, true));
2170            if let Some(node) = self.node(id) {
2171                for child in node.children().rev() {
2172                    stack.push((child, false));
2173                }
2174            }
2175        }
2176        order
2177    }
2178
2179    /// Return a copy of this graph with the named scalar parameter fixed.
2180    ///
2181    /// # Errors
2182    ///
2183    /// Returns [`ParamError::UnknownName`] when the graph has no scalar
2184    /// parameter named `name`, or [`ParamError::FixedValueOutOfBounds`] when
2185    /// `value` is outside that parameter's bounds.
2186    pub fn fix_parameter(&self, name: &str, value: f64) -> ParamResult<Self> {
2187        self.map_parameter(name, |parameter| {
2188            if !parameter.bounds_spec().contains(value) {
2189                return Err(ParamError::FixedValueOutOfBounds {
2190                    name: name.to_owned(),
2191                    value,
2192                });
2193            }
2194            Ok(parameter.clone().with_fixed_value(value))
2195        })
2196    }
2197
2198    /// Return a copy of this graph with the named scalar parameter free.
2199    ///
2200    /// # Errors
2201    ///
2202    /// Returns [`ParamError::UnknownName`] when the graph has no scalar
2203    /// parameter named `name`.
2204    pub fn free_parameter(&self, name: &str) -> ParamResult<Self> {
2205        self.map_parameter(name, |parameter| Ok(parameter.clone().with_free()))
2206    }
2207
2208    fn map_parameter(
2209        &self,
2210        name: &str,
2211        mut map: impl FnMut(&Parameter) -> ParamResult<Parameter>,
2212    ) -> ParamResult<Self> {
2213        let mut found = false;
2214        let mut graph = self.clone();
2215        for node in &mut graph.nodes {
2216            if let ExprNode::ScalarParam(parameter) = node
2217                && parameter.name() == name
2218            {
2219                *parameter = map(parameter)?;
2220                found = true;
2221            }
2222        }
2223        if !found {
2224            return Err(ParamError::UnknownName(name.to_owned()));
2225        }
2226        Ok(graph)
2227    }
2228
2229    /// Replaces tagged components that match none of `tags` with zero.
2230    ///
2231    /// Untagged nodes remain active, and a matching tagged node retains its
2232    /// entire subtree.
2233    ///
2234    /// # Panics
2235    ///
2236    /// Panics only if an internal graph-rebuild invariant is violated.
2237    pub fn project_tags<'a>(&self, tags: impl IntoIterator<Item = &'a str>) -> Self {
2238        let tags: Vec<_> = tags.into_iter().collect();
2239        let mut rebuild = ExprGraphRebuilder::with_capacity(self.nodes.len());
2240        let root_key = (self.root, false);
2241        let mut visited = HashSet::with_capacity(self.nodes.len());
2242        let mut stack = vec![(root_key, false)];
2243        while let Some((key @ (old, retain_all), expanded)) = stack.pop() {
2244            if expanded {
2245                let old_metadata = &self.metadata[old.index()];
2246                let matches = old_metadata
2247                    .tags
2248                    .iter()
2249                    .any(|tag| tags.contains(&tag.as_ref()));
2250                let node = if !retain_all && !old_metadata.tags.is_empty() && !matches {
2251                    ExprNode::RealConst(0.0)
2252                } else {
2253                    let retain_children = retain_all || matches;
2254                    self.nodes[old.index()].map_children(|child| {
2255                        rebuild
2256                            .remapped(&(child, retain_children))
2257                            .expect("tag projection emits children before parents")
2258                    })
2259                };
2260                let metadata = if matches || retain_all {
2261                    old_metadata.clone()
2262                } else {
2263                    ExprMetadata::new(old_metadata.source)
2264                };
2265                rebuild.emit(key, node, metadata);
2266                continue;
2267            }
2268            if !visited.insert(key) {
2269                continue;
2270            }
2271            stack.push((key, true));
2272            let old_metadata = &self.metadata[old.index()];
2273            let matches = old_metadata
2274                .tags
2275                .iter()
2276                .any(|tag| tags.contains(&tag.as_ref()));
2277            if retain_all || old_metadata.tags.is_empty() || matches {
2278                let retain_children = retain_all || matches;
2279                for child in self.nodes[old.index()].children().rev() {
2280                    stack.push(((child, retain_children), false));
2281                }
2282            }
2283        }
2284        let root = rebuild
2285            .remapped(&root_key)
2286            .expect("tag projection emits its root");
2287        rebuild
2288            .finish(root)
2289            .expect("tag projection rebuilds a valid expression graph")
2290    }
2291
2292    /// Validates and constructs a graph from its serialized parts.
2293    ///
2294    /// Child nodes must precede their parents, metadata must have one entry per
2295    /// node, and `root` must identify an existing node.
2296    ///
2297    /// # Errors
2298    ///
2299    /// Returns [`ExprGraphError`] when the graph is empty, the metadata and
2300    /// node lengths differ, `root` is invalid, or a child identifier is
2301    /// invalid or does not precede its parent.
2302    pub fn from_parts(
2303        root: ExprId,
2304        nodes: Vec<ExprNode>,
2305        metadata: Vec<ExprMetadata>,
2306    ) -> Result<Self, ExprGraphError> {
2307        if nodes.is_empty() {
2308            return Err(ExprGraphError::Empty);
2309        }
2310        if nodes.len() != metadata.len() {
2311            return Err(ExprGraphError::MetadataLength {
2312                node_len: nodes.len(),
2313                metadata_len: metadata.len(),
2314            });
2315        }
2316        if root.index() >= nodes.len() {
2317            return Err(ExprGraphError::InvalidRoot {
2318                root: root.index(),
2319                node_len: nodes.len(),
2320            });
2321        }
2322        for (index, node) in nodes.iter().enumerate() {
2323            for child in node.children() {
2324                if child.index() >= nodes.len() {
2325                    return Err(ExprGraphError::InvalidChild {
2326                        node: index,
2327                        child: child.index(),
2328                    });
2329                }
2330                if child.index() >= index {
2331                    return Err(ExprGraphError::InvalidChildOrder {
2332                        node: index,
2333                        child: child.index(),
2334                    });
2335                }
2336            }
2337        }
2338        Ok(Self {
2339            root,
2340            nodes,
2341            metadata,
2342        })
2343    }
2344
2345    /// Returns the root node identifier.
2346    pub fn root(&self) -> ExprId {
2347        self.root
2348    }
2349
2350    /// Returns the node identified by `id`, if it exists.
2351    pub fn node(&self, id: ExprId) -> Option<&ExprNode> {
2352        self.nodes.get(id.index())
2353    }
2354
2355    /// Returns all nodes in topological order.
2356    pub fn nodes(&self) -> &[ExprNode] {
2357        &self.nodes
2358    }
2359
2360    /// Returns the metadata associated with `id`, if it exists.
2361    pub fn metadata(&self, id: ExprId) -> Option<&ExprMetadata> {
2362        self.metadata.get(id.index())
2363    }
2364}
2365
2366pub(crate) fn node_children(node: &ExprNode) -> Vec<(String, ExprId)> {
2367    node.children()
2368        .enumerate()
2369        .map(|(index, child)| (node_child_label(node, index), child))
2370        .collect()
2371}
2372
2373fn node_child_label(node: &ExprNode, index: usize) -> String {
2374    match node {
2375        ExprNode::Unary { .. } | ExprNode::Component { .. } | ExprNode::MatrixElement { .. } => {
2376            "input".into()
2377        }
2378        ExprNode::Binary { .. } | ExprNode::MatMul { .. } | ExprNode::Dot { .. } => {
2379            if index == 0 { "lhs" } else { "rhs" }.into()
2380        }
2381        ExprNode::NaryAdd { .. } => format!("term[{index}]"),
2382        ExprNode::NaryMul { .. } => format!("factor[{index}]"),
2383        ExprNode::Complex { .. } => if index == 0 { "re" } else { "im" }.into(),
2384        ExprNode::Vector { .. } => format!("element[{index}]"),
2385        ExprNode::Matrix { cols, .. } => {
2386            format!("element[{},{}]", index / cols, index % cols)
2387        }
2388        ExprNode::MatVec { .. } => if index == 0 { "matrix" } else { "vector" }.into(),
2389        ExprNode::Solve { .. } => if index == 0 { "matrix" } else { "rhs" }.into(),
2390        ExprNode::RealConst(_)
2391        | ExprNode::ComplexConst(_)
2392        | ExprNode::ScalarParam(_)
2393        | ExprNode::EventScalar(_)
2394        | ExprNode::EventP4Component { .. } => unreachable!("leaf nodes have no child labels"),
2395    }
2396}
2397
2398#[derive(Default)]
2399struct GraphBuilder {
2400    nodes: Vec<ExprNode>,
2401    metadata: Vec<ExprMetadata>,
2402    ids: HashMap<usize, ExprId>,
2403}
2404
2405impl GraphBuilder {
2406    fn new() -> Self {
2407        Self::default()
2408    }
2409
2410    fn build(mut self, expr: &Expr) -> ExprGraph {
2411        let mut stack = vec![(expr.clone(), false)];
2412        while let Some((expr, expanded)) = stack.pop() {
2413            let key = Arc::as_ptr(&expr.node) as usize;
2414            if self.ids.contains_key(&key) {
2415                continue;
2416            }
2417            if expanded {
2418                let node = self.lower(&expr.node.kind);
2419                let id = ExprId::from_index(self.nodes.len());
2420                self.nodes.push(node);
2421                self.metadata.push(expr.node.metadata.clone());
2422                self.ids.insert(key, id);
2423                continue;
2424            }
2425            stack.push((expr.clone(), true));
2426            for index in (0..expr.node.kind.child_count()).rev() {
2427                stack.push((expr.node.kind.child_at(index).clone(), false));
2428            }
2429        }
2430        let root = self.id(expr);
2431        ExprGraph {
2432            root,
2433            nodes: self.nodes,
2434            metadata: self.metadata,
2435        }
2436    }
2437
2438    fn id(&self, expr: &Expr) -> ExprId {
2439        let key = Arc::as_ptr(&expr.node) as usize;
2440        self.ids[&key]
2441    }
2442
2443    fn lower(&self, kind: &DagNodeKind) -> ExprNode {
2444        match kind {
2445            DagNodeKind::RealConst(value) => ExprNode::RealConst(*value),
2446            DagNodeKind::ComplexConst(value) => ExprNode::ComplexConst(*value),
2447            DagNodeKind::ScalarParam(parameter) => ExprNode::ScalarParam(parameter.clone()),
2448            DagNodeKind::EventScalar(name) => ExprNode::EventScalar(Arc::clone(name)),
2449            DagNodeKind::EventP4Component { name, component } => ExprNode::EventP4Component {
2450                name: Arc::clone(name),
2451                component: *component,
2452            },
2453            DagNodeKind::Unary { op, input } => {
2454                let input = self.id(input);
2455                ExprNode::Unary { op: *op, input }
2456            }
2457            DagNodeKind::Binary { op, lhs, rhs } => {
2458                let lhs = self.id(lhs);
2459                let rhs = self.id(rhs);
2460                ExprNode::Binary { op: *op, lhs, rhs }
2461            }
2462            DagNodeKind::Complex { re, im } => {
2463                let re = self.id(re);
2464                let im = self.id(im);
2465                ExprNode::Complex { re, im }
2466            }
2467            DagNodeKind::Vector { elements } => ExprNode::Vector {
2468                elements: elements.iter().map(|expr| self.id(expr)).collect(),
2469            },
2470            DagNodeKind::Matrix {
2471                rows,
2472                cols,
2473                elements,
2474            } => ExprNode::Matrix {
2475                rows: *rows,
2476                cols: *cols,
2477                elements: elements.iter().map(|expr| self.id(expr)).collect(),
2478            },
2479            DagNodeKind::Component { input, index } => {
2480                let input = self.id(input);
2481                ExprNode::Component {
2482                    input,
2483                    index: *index,
2484                }
2485            }
2486            DagNodeKind::MatrixElement { input, row, col } => {
2487                let input = self.id(input);
2488                ExprNode::MatrixElement {
2489                    input,
2490                    row: *row,
2491                    col: *col,
2492                }
2493            }
2494            DagNodeKind::MatMul { lhs, rhs } => {
2495                let lhs = self.id(lhs);
2496                let rhs = self.id(rhs);
2497                ExprNode::MatMul { lhs, rhs }
2498            }
2499            DagNodeKind::MatVec { matrix, vector } => {
2500                let matrix = self.id(matrix);
2501                let vector = self.id(vector);
2502                ExprNode::MatVec { matrix, vector }
2503            }
2504            DagNodeKind::Dot { lhs, rhs } => {
2505                let lhs = self.id(lhs);
2506                let rhs = self.id(rhs);
2507                ExprNode::Dot { lhs, rhs }
2508            }
2509            DagNodeKind::Solve { matrix, rhs } => {
2510                let matrix = self.id(matrix);
2511                let rhs = self.id(rhs);
2512                ExprNode::Solve { matrix, rhs }
2513            }
2514        }
2515    }
2516}
2517
2518fn source_kind(kind: &DagNodeKind) -> ExprSourceKind {
2519    match kind {
2520        DagNodeKind::RealConst(_) | DagNodeKind::ComplexConst(_) => ExprSourceKind::Const,
2521        DagNodeKind::ScalarParam(_) => ExprSourceKind::Param,
2522        DagNodeKind::EventScalar(_) | DagNodeKind::EventP4Component { .. } => ExprSourceKind::Event,
2523        DagNodeKind::Unary { .. } => ExprSourceKind::Unary,
2524        DagNodeKind::Binary { .. } => ExprSourceKind::Binary,
2525        DagNodeKind::Complex { .. } => ExprSourceKind::Complex,
2526        DagNodeKind::Vector { .. } | DagNodeKind::Component { .. } | DagNodeKind::Dot { .. } => {
2527            ExprSourceKind::Vector
2528        }
2529        DagNodeKind::Matrix { .. } | DagNodeKind::MatrixElement { .. } => ExprSourceKind::Matrix,
2530        DagNodeKind::MatMul { .. } | DagNodeKind::MatVec { .. } | DagNodeKind::Solve { .. } => {
2531            ExprSourceKind::LinearAlgebra
2532        }
2533    }
2534}
2535
2536#[cfg(test)]
2537mod tests {
2538    use super::*;
2539    use crate::parameter;
2540
2541    #[test]
2542    fn builds_target_syntax_without_layout_or_context() {
2543        let model = (Complex64::I * parameter!("y", initial : 1.0, bounds : (0.0, 2.0))
2544            + parameter!("x"))
2545        .norm_sqr();
2546
2547        let graph = model.to_graph();
2548        assert!(matches!(
2549            graph.node(graph.root()),
2550            Some(ExprNode::Unary {
2551                op: UnaryOp::NormSqr,
2552                ..
2553            })
2554        ));
2555    }
2556
2557    #[test]
2558    fn parameter_nodes_store_specs_but_do_not_make_layouts() {
2559        let graph = Expr::from(parameter!("x", initial: 1.0)).to_graph();
2560        assert!(matches!(
2561            graph.node(graph.root()),
2562            Some(ExprNode::ScalarParam(spec)) if spec.name() == "x"
2563        ));
2564    }
2565
2566    #[test]
2567    fn complex_constructor_builds_expression_node() {
2568        let graph = complex(parameter!("re"), parameter!("im")).to_graph();
2569
2570        assert!(matches!(
2571            graph.node(graph.root()),
2572            Some(ExprNode::Complex { .. })
2573        ));
2574    }
2575
2576    #[test]
2577    fn polar_complex_lowers_to_expression_graph() {
2578        let graph = polar_complex(parameter!("mag"), parameter!("phase")).to_graph();
2579
2580        assert!(graph.nodes().iter().any(|node| matches!(
2581            node,
2582            ExprNode::Unary {
2583                op: UnaryOp::Exp,
2584                ..
2585            }
2586        )));
2587    }
2588
2589    #[test]
2590    fn metadata_survives_graph_construction() {
2591        let graph = event_scalar("mass")
2592            .named("event mass")
2593            .tagged("data")
2594            .tagged("data")
2595            .to_graph();
2596        let metadata = graph.metadata(graph.root()).unwrap();
2597        assert_eq!(metadata.name(), Some("event mass"));
2598        assert_eq!(metadata.tags(), &[Arc::from("data")]);
2599        assert!(metadata.has_tag("data"));
2600    }
2601
2602    #[test]
2603    fn expressions_round_trip_through_serde_with_metadata() {
2604        let expression = ((parameter!("x", initial: 1.0) + 2.0).named("offset")
2605            * event_scalar("mass").tagged("data"))
2606        .tagged("model");
2607        let encoded = serde_json::to_string(&expression).unwrap();
2608        let decoded: Expr = serde_json::from_str(&encoded).unwrap();
2609
2610        assert_eq!(
2611            serde_json::to_value(expression.to_graph()).unwrap(),
2612            serde_json::to_value(decoded.to_graph()).unwrap()
2613        );
2614    }
2615
2616    #[test]
2617    fn display_formats_graph_as_labeled_tree() {
2618        let graph = ((parameter!("x") + 1.0).named("offset") * event_scalar("mass").tagged("data"))
2619            .to_graph();
2620        let display = graph.display_tree().to_string();
2621
2622        assert!(display.starts_with("ExprGraph(root=#"));
2623        assert!(display.contains("Binary(Mul)"));
2624        assert!(display.contains("┣ lhs:"));
2625        assert!(display.contains("┗ rhs:"));
2626        assert!(display.contains("Binary(Add) name=\"offset\""));
2627        assert!(display.contains("ScalarParam(x)"));
2628        assert!(display.contains("RealConst(1)"));
2629        assert!(display.contains("EventScalar(mass) tags=[data]"));
2630    }
2631
2632    #[test]
2633    fn display_formats_graph_as_expression() {
2634        let costheta = Expr::from(parameter!("costheta"));
2635        let phi = event_scalar("phi");
2636        let p = Expr::from(parameter!("p"));
2637        let phase = Expr::from(7.0) * Complex64::I;
2638        let graph =
2639            (((costheta.powi(2) * phi.sin()) - 5.2).norm_sqr() * p.conj() - phase.exp()).to_graph();
2640
2641        assert_eq!(
2642            graph.to_string(),
2643            "|costheta^2 * sin(phi) - 5.2|^2 * conj(p) - exp(7 * i)"
2644        );
2645    }
2646
2647    #[test]
2648    fn display_parenthesizes_when_precedence_requires_it() {
2649        let a = Expr::from(parameter!("a"));
2650        let b = Expr::from(parameter!("b"));
2651        let c = Expr::from(parameter!("c"));
2652
2653        assert_eq!(
2654            (a.clone() * (b.clone() + c.clone())).to_graph().to_string(),
2655            "a * (b + c)"
2656        );
2657        assert_eq!(
2658            (a.clone() - (b.clone() - c.clone())).to_graph().to_string(),
2659            "a - (b - c)"
2660        );
2661        assert_eq!(((a / b) / c).to_graph().to_string(), "a / b / c");
2662    }
2663
2664    #[test]
2665    fn display_rounds_tiny_float_representation_noise() {
2666        let metadata = ExprMetadata::new(ExprSourceKind::Const);
2667        let graph = ExprGraph::from_parts(
2668            ExprId::from_index(2),
2669            vec![
2670                ExprNode::RealConst(2.9999999999999996),
2671                ExprNode::ComplexConst(Complex64::new(0.30000000000000004, 1.9999999999999998)),
2672                ExprNode::Binary {
2673                    op: BinaryOp::Add,
2674                    lhs: ExprId::from_index(0),
2675                    rhs: ExprId::from_index(1),
2676                },
2677            ],
2678            vec![metadata.clone(), metadata.clone(), metadata],
2679        )
2680        .unwrap();
2681
2682        assert_eq!(graph.to_string(), "3 + 0.3 + 2i");
2683        assert!(graph.display_tree().to_string().contains("RealConst(3)"));
2684        assert!(
2685            graph
2686                .display_tree()
2687                .to_string()
2688                .contains("ComplexConst(0.3 + 2i)")
2689        );
2690    }
2691
2692    #[test]
2693    fn display_formats_p4_components_and_atan2() {
2694        let expr = atan2(
2695            event_p4_component("ks1", P4Component::Py),
2696            event_p4_component("ks1", P4Component::Px),
2697        );
2698
2699        assert_eq!(expr.to_graph().to_string(), "atan2(ks1.py, ks1.px)");
2700    }
2701
2702    #[test]
2703    fn graph_from_parts_validates_structure() {
2704        let metadata = ExprMetadata::new(ExprSourceKind::Const);
2705        let graph = ExprGraph::from_parts(
2706            ExprId::from_index(1),
2707            vec![
2708                ExprNode::RealConst(1.0),
2709                ExprNode::Unary {
2710                    op: UnaryOp::Neg,
2711                    input: ExprId::from_index(0),
2712                },
2713            ],
2714            vec![metadata.clone(), metadata.clone()],
2715        )
2716        .unwrap();
2717        assert!(matches!(
2718            graph.node(graph.root()),
2719            Some(ExprNode::Unary {
2720                op: UnaryOp::Neg,
2721                ..
2722            })
2723        ));
2724
2725        let err = ExprGraph::from_parts(
2726            ExprId::from_index(0),
2727            vec![ExprNode::RealConst(1.0)],
2728            Vec::new(),
2729        )
2730        .unwrap_err();
2731        assert!(matches!(err, ExprGraphError::MetadataLength { .. }));
2732
2733        let err = ExprGraph::from_parts(
2734            ExprId::from_index(0),
2735            vec![ExprNode::Unary {
2736                op: UnaryOp::Neg,
2737                input: ExprId::from_index(0),
2738            }],
2739            vec![metadata],
2740        )
2741        .unwrap_err();
2742        assert!(matches!(err, ExprGraphError::InvalidChildOrder { .. }));
2743    }
2744
2745    #[test]
2746    fn graph_preserves_unsimplified_expression_shape() {
2747        let graph = (parameter!("x") + 0.0).to_graph();
2748        assert!(matches!(
2749            graph.node(graph.root()),
2750            Some(ExprNode::Binary {
2751                op: BinaryOp::Add,
2752                ..
2753            })
2754        ));
2755    }
2756
2757    #[test]
2758    fn graph_preserves_written_operand_order_for_commutative_ops() {
2759        let left_param = (parameter!("x") + 1.0).to_graph();
2760        assert!(matches!(
2761            left_param.node(left_param.root()),
2762            Some(ExprNode::Binary {
2763                op: BinaryOp::Add,
2764                lhs,
2765                rhs
2766            }) if matches!(left_param.node(*lhs), Some(ExprNode::ScalarParam(parameter)) if parameter.name() == "x")
2767                && matches!(left_param.node(*rhs), Some(ExprNode::RealConst(1.0)))
2768        ));
2769
2770        let right_param = (1.0 + parameter!("x")).to_graph();
2771        assert!(matches!(
2772            right_param.node(right_param.root()),
2773            Some(ExprNode::Binary {
2774                op: BinaryOp::Add,
2775                lhs,
2776                rhs
2777            }) if matches!(right_param.node(*lhs), Some(ExprNode::RealConst(1.0)))
2778                && matches!(right_param.node(*rhs), Some(ExprNode::ScalarParam(parameter)) if parameter.name() == "x")
2779        ));
2780    }
2781
2782    #[test]
2783    fn represents_kmatrix_style_solve_graph() {
2784        let beta = vector([
2785            complex(parameter!("b0_re"), parameter!("b0_im")),
2786            complex(parameter!("b1_re"), parameter!("b1_im")),
2787        ]);
2788        let a = matrix([
2789            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)],
2790            [Complex64::new(0.0, -1.0), Complex64::new(1.0, 0.0)],
2791        ]);
2792        let graph = solve(a, beta).component(0).to_graph();
2793
2794        assert!(
2795            graph
2796                .nodes()
2797                .iter()
2798                .any(|node| matches!(node, ExprNode::Solve { .. }))
2799        );
2800    }
2801
2802    #[test]
2803    fn graph_builder_preserves_shared_dag_nodes() {
2804        let shared = event_scalar("x").sin();
2805        let expression = vector((0..1_000).map(|_| shared.clone()));
2806        let graph = expression.to_graph();
2807
2808        assert_eq!(graph.nodes().len(), 3);
2809        let ExprNode::Vector { elements } = graph.node(graph.root()).unwrap() else {
2810            panic!("root should be a vector");
2811        };
2812        assert!(elements.windows(2).all(|pair| pair[0] == pair[1]));
2813    }
2814
2815    #[test]
2816    fn expression_projection_preserves_occurrence_rebuild_behavior() {
2817        let shared = event_scalar("x").sin();
2818        let projected = (shared.clone() + shared).project_tags(["selected"]);
2819        let graph = projected.to_graph();
2820
2821        assert_eq!(
2822            graph
2823                .nodes()
2824                .iter()
2825                .filter(|node| matches!(
2826                    node,
2827                    ExprNode::Unary {
2828                        op: UnaryOp::Sin,
2829                        ..
2830                    }
2831                ))
2832                .count(),
2833            2
2834        );
2835    }
2836
2837    #[test]
2838    fn iterative_construction_and_projection_handle_deep_expressions() {
2839        let mut expression = event_scalar("x");
2840        for _ in 0..10_000 {
2841            expression = expression.sin();
2842        }
2843
2844        let projected = expression.project_tags(["selected"]);
2845        let graph = projected.to_graph();
2846
2847        assert_eq!(graph.nodes().len(), 10_001);
2848        assert_eq!(
2849            graph.reachable_post_order([graph.root()]).len(),
2850            graph.nodes().len()
2851        );
2852
2853        // Deep `Arc` chains also recurse when their final owner is dropped;
2854        // this test targets traversal behavior rather than destructor policy.
2855        std::mem::forget(expression);
2856        std::mem::forget(projected);
2857    }
2858
2859    #[test]
2860    fn reachable_post_order_preserves_child_order_and_deduplicates_shared_nodes() {
2861        let shared = event_scalar("x").sin();
2862        let graph = (shared.clone() + shared).to_graph();
2863        let order = graph.reachable_post_order([graph.root()]);
2864
2865        assert_eq!(order.len(), graph.nodes().len());
2866        assert_eq!(order.last(), Some(&graph.root()));
2867        for id in order {
2868            for child in graph.node(id).unwrap().children() {
2869                assert!(child.index() < id.index());
2870            }
2871        }
2872    }
2873
2874    #[test]
2875    fn dynamic_matrices_and_shapes_are_checked_eagerly() {
2876        let dynamic = matrix_from_flat(2, 2, [1.0, 2.0, 3.0, 4.0]).unwrap();
2877        assert_eq!(
2878            dynamic.shape().unwrap(),
2879            ExprShape::Matrix { rows: 2, cols: 2 }
2880        );
2881        assert!(matrix_from_flat(2, 2, [1.0, 2.0, 3.0]).is_err());
2882        assert!(matmul(dynamic, matrix([[1.0, 2.0, 3.0]])).shape().is_err());
2883    }
2884
2885    #[test]
2886    fn assignment_operators_build_binary_expression_nodes() {
2887        let mut expr = Expr::from(parameter!("x"));
2888        expr += parameter!("y");
2889        expr -= 1.0;
2890        expr *= Complex64::I;
2891        expr /= Expr::from(parameter!("z"));
2892
2893        let graph = expr.to_graph();
2894        assert!(matches!(
2895            graph.node(graph.root()),
2896            Some(ExprNode::Binary {
2897                op: BinaryOp::Div,
2898                ..
2899            })
2900        ));
2901        assert_eq!(
2902            graph
2903                .nodes()
2904                .iter()
2905                .filter(|node| matches!(node, ExprNode::Binary { .. }))
2906                .count(),
2907            4
2908        );
2909    }
2910
2911    #[test]
2912    fn assignment_operators_accept_borrowed_rhs_values() {
2913        let y = parameter!("y");
2914        let one = 1.0;
2915        let i = Complex64::I;
2916        let z = Expr::from(parameter!("z"));
2917
2918        let mut expr = Expr::from(parameter!("x"));
2919        expr += &y;
2920        expr -= &one;
2921        expr *= &i;
2922        expr /= &z;
2923
2924        let graph = expr.to_graph();
2925        assert!(matches!(
2926            graph.node(graph.root()),
2927            Some(ExprNode::Binary {
2928                op: BinaryOp::Div,
2929                ..
2930            })
2931        ));
2932    }
2933}