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, ParameterUpdate},
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 a batch of parameter updates applied.
2180    ///
2181    /// Updates are keyed by parameter name and every occurrence of a named
2182    /// scalar parameter is updated. The operation is atomic: duplicate or
2183    /// unknown names, invalid patches, conflicts, and invalid final
2184    /// definitions leave the original graph untouched.
2185    ///
2186    /// # Errors
2187    ///
2188    /// Returns [`ParamError`] when an update is duplicated, unknown, invalid,
2189    /// or leaves parameter definitions in conflict.
2190    pub fn with_parameters<N, I>(&self, updates: I) -> ParamResult<Self>
2191    where
2192        N: AsRef<str>,
2193        I: IntoIterator<Item = (N, ParameterUpdate)>,
2194    {
2195        let mut by_name = HashMap::new();
2196        let mut names = Vec::new();
2197        for (name, update) in updates {
2198            let name = name.as_ref().to_owned();
2199            update.validate()?;
2200            if by_name.insert(name.clone(), update).is_some() {
2201                return Err(ParamError::DuplicateName(name));
2202            }
2203            names.push(name);
2204        }
2205
2206        let mut found = HashSet::new();
2207        let mut graph = self.clone();
2208        for node in &mut graph.nodes {
2209            if let ExprNode::ScalarParam(parameter) = node
2210                && let Some(update) = by_name.get(parameter.name())
2211            {
2212                *parameter = parameter.with_update(update)?;
2213                found.insert(parameter.name().to_owned());
2214            }
2215        }
2216        if let Some(name) = names.iter().find(|name| !found.contains(*name)) {
2217            return Err(ParamError::UnknownName(name.clone()));
2218        }
2219
2220        let mut registry = crate::parameters::ParamRegistry::new();
2221        for node in &graph.nodes {
2222            if let ExprNode::ScalarParam(parameter) = node {
2223                registry.register(parameter.clone())?;
2224            }
2225        }
2226        registry.layout()?;
2227        Ok(graph)
2228    }
2229
2230    /// Replaces tagged components that match none of `tags` with zero.
2231    ///
2232    /// Untagged nodes remain active, and a matching tagged node retains its
2233    /// entire subtree.
2234    ///
2235    /// # Panics
2236    ///
2237    /// Panics only if an internal graph-rebuild invariant is violated.
2238    pub fn project_tags<'a>(&self, tags: impl IntoIterator<Item = &'a str>) -> Self {
2239        let tags: Vec<_> = tags.into_iter().collect();
2240        let mut rebuild = ExprGraphRebuilder::with_capacity(self.nodes.len());
2241        let root_key = (self.root, false);
2242        let mut visited = HashSet::with_capacity(self.nodes.len());
2243        let mut stack = vec![(root_key, false)];
2244        while let Some((key @ (old, retain_all), expanded)) = stack.pop() {
2245            if expanded {
2246                let old_metadata = &self.metadata[old.index()];
2247                let matches = old_metadata
2248                    .tags
2249                    .iter()
2250                    .any(|tag| tags.contains(&tag.as_ref()));
2251                let node = if !retain_all && !old_metadata.tags.is_empty() && !matches {
2252                    ExprNode::RealConst(0.0)
2253                } else {
2254                    let retain_children = retain_all || matches;
2255                    self.nodes[old.index()].map_children(|child| {
2256                        rebuild
2257                            .remapped(&(child, retain_children))
2258                            .expect("tag projection emits children before parents")
2259                    })
2260                };
2261                let metadata = if matches || retain_all {
2262                    old_metadata.clone()
2263                } else {
2264                    ExprMetadata::new(old_metadata.source)
2265                };
2266                rebuild.emit(key, node, metadata);
2267                continue;
2268            }
2269            if !visited.insert(key) {
2270                continue;
2271            }
2272            stack.push((key, true));
2273            let old_metadata = &self.metadata[old.index()];
2274            let matches = old_metadata
2275                .tags
2276                .iter()
2277                .any(|tag| tags.contains(&tag.as_ref()));
2278            if retain_all || old_metadata.tags.is_empty() || matches {
2279                let retain_children = retain_all || matches;
2280                for child in self.nodes[old.index()].children().rev() {
2281                    stack.push(((child, retain_children), false));
2282                }
2283            }
2284        }
2285        let root = rebuild
2286            .remapped(&root_key)
2287            .expect("tag projection emits its root");
2288        rebuild
2289            .finish(root)
2290            .expect("tag projection rebuilds a valid expression graph")
2291    }
2292
2293    /// Validates and constructs a graph from its serialized parts.
2294    ///
2295    /// Child nodes must precede their parents, metadata must have one entry per
2296    /// node, and `root` must identify an existing node.
2297    ///
2298    /// # Errors
2299    ///
2300    /// Returns [`ExprGraphError`] when the graph is empty, the metadata and
2301    /// node lengths differ, `root` is invalid, or a child identifier is
2302    /// invalid or does not precede its parent.
2303    pub fn from_parts(
2304        root: ExprId,
2305        nodes: Vec<ExprNode>,
2306        metadata: Vec<ExprMetadata>,
2307    ) -> Result<Self, ExprGraphError> {
2308        if nodes.is_empty() {
2309            return Err(ExprGraphError::Empty);
2310        }
2311        if nodes.len() != metadata.len() {
2312            return Err(ExprGraphError::MetadataLength {
2313                node_len: nodes.len(),
2314                metadata_len: metadata.len(),
2315            });
2316        }
2317        if root.index() >= nodes.len() {
2318            return Err(ExprGraphError::InvalidRoot {
2319                root: root.index(),
2320                node_len: nodes.len(),
2321            });
2322        }
2323        for (index, node) in nodes.iter().enumerate() {
2324            for child in node.children() {
2325                if child.index() >= nodes.len() {
2326                    return Err(ExprGraphError::InvalidChild {
2327                        node: index,
2328                        child: child.index(),
2329                    });
2330                }
2331                if child.index() >= index {
2332                    return Err(ExprGraphError::InvalidChildOrder {
2333                        node: index,
2334                        child: child.index(),
2335                    });
2336                }
2337            }
2338        }
2339        Ok(Self {
2340            root,
2341            nodes,
2342            metadata,
2343        })
2344    }
2345
2346    /// Returns the root node identifier.
2347    pub fn root(&self) -> ExprId {
2348        self.root
2349    }
2350
2351    /// Returns the node identified by `id`, if it exists.
2352    pub fn node(&self, id: ExprId) -> Option<&ExprNode> {
2353        self.nodes.get(id.index())
2354    }
2355
2356    /// Returns all nodes in topological order.
2357    pub fn nodes(&self) -> &[ExprNode] {
2358        &self.nodes
2359    }
2360
2361    /// Returns the metadata associated with `id`, if it exists.
2362    pub fn metadata(&self, id: ExprId) -> Option<&ExprMetadata> {
2363        self.metadata.get(id.index())
2364    }
2365}
2366
2367pub(crate) fn node_children(node: &ExprNode) -> Vec<(String, ExprId)> {
2368    node.children()
2369        .enumerate()
2370        .map(|(index, child)| (node_child_label(node, index), child))
2371        .collect()
2372}
2373
2374fn node_child_label(node: &ExprNode, index: usize) -> String {
2375    match node {
2376        ExprNode::Unary { .. } | ExprNode::Component { .. } | ExprNode::MatrixElement { .. } => {
2377            "input".into()
2378        }
2379        ExprNode::Binary { .. } | ExprNode::MatMul { .. } | ExprNode::Dot { .. } => {
2380            if index == 0 { "lhs" } else { "rhs" }.into()
2381        }
2382        ExprNode::NaryAdd { .. } => format!("term[{index}]"),
2383        ExprNode::NaryMul { .. } => format!("factor[{index}]"),
2384        ExprNode::Complex { .. } => if index == 0 { "re" } else { "im" }.into(),
2385        ExprNode::Vector { .. } => format!("element[{index}]"),
2386        ExprNode::Matrix { cols, .. } => {
2387            format!("element[{},{}]", index / cols, index % cols)
2388        }
2389        ExprNode::MatVec { .. } => if index == 0 { "matrix" } else { "vector" }.into(),
2390        ExprNode::Solve { .. } => if index == 0 { "matrix" } else { "rhs" }.into(),
2391        ExprNode::RealConst(_)
2392        | ExprNode::ComplexConst(_)
2393        | ExprNode::ScalarParam(_)
2394        | ExprNode::EventScalar(_)
2395        | ExprNode::EventP4Component { .. } => unreachable!("leaf nodes have no child labels"),
2396    }
2397}
2398
2399#[derive(Default)]
2400struct GraphBuilder {
2401    nodes: Vec<ExprNode>,
2402    metadata: Vec<ExprMetadata>,
2403    ids: HashMap<usize, ExprId>,
2404}
2405
2406impl GraphBuilder {
2407    fn new() -> Self {
2408        Self::default()
2409    }
2410
2411    fn build(mut self, expr: &Expr) -> ExprGraph {
2412        let mut stack = vec![(expr.clone(), false)];
2413        while let Some((expr, expanded)) = stack.pop() {
2414            let key = Arc::as_ptr(&expr.node) as usize;
2415            if self.ids.contains_key(&key) {
2416                continue;
2417            }
2418            if expanded {
2419                let node = self.lower(&expr.node.kind);
2420                let id = ExprId::from_index(self.nodes.len());
2421                self.nodes.push(node);
2422                self.metadata.push(expr.node.metadata.clone());
2423                self.ids.insert(key, id);
2424                continue;
2425            }
2426            stack.push((expr.clone(), true));
2427            for index in (0..expr.node.kind.child_count()).rev() {
2428                stack.push((expr.node.kind.child_at(index).clone(), false));
2429            }
2430        }
2431        let root = self.id(expr);
2432        ExprGraph {
2433            root,
2434            nodes: self.nodes,
2435            metadata: self.metadata,
2436        }
2437    }
2438
2439    fn id(&self, expr: &Expr) -> ExprId {
2440        let key = Arc::as_ptr(&expr.node) as usize;
2441        self.ids[&key]
2442    }
2443
2444    fn lower(&self, kind: &DagNodeKind) -> ExprNode {
2445        match kind {
2446            DagNodeKind::RealConst(value) => ExprNode::RealConst(*value),
2447            DagNodeKind::ComplexConst(value) => ExprNode::ComplexConst(*value),
2448            DagNodeKind::ScalarParam(parameter) => ExprNode::ScalarParam(parameter.clone()),
2449            DagNodeKind::EventScalar(name) => ExprNode::EventScalar(Arc::clone(name)),
2450            DagNodeKind::EventP4Component { name, component } => ExprNode::EventP4Component {
2451                name: Arc::clone(name),
2452                component: *component,
2453            },
2454            DagNodeKind::Unary { op, input } => {
2455                let input = self.id(input);
2456                ExprNode::Unary { op: *op, input }
2457            }
2458            DagNodeKind::Binary { op, lhs, rhs } => {
2459                let lhs = self.id(lhs);
2460                let rhs = self.id(rhs);
2461                ExprNode::Binary { op: *op, lhs, rhs }
2462            }
2463            DagNodeKind::Complex { re, im } => {
2464                let re = self.id(re);
2465                let im = self.id(im);
2466                ExprNode::Complex { re, im }
2467            }
2468            DagNodeKind::Vector { elements } => ExprNode::Vector {
2469                elements: elements.iter().map(|expr| self.id(expr)).collect(),
2470            },
2471            DagNodeKind::Matrix {
2472                rows,
2473                cols,
2474                elements,
2475            } => ExprNode::Matrix {
2476                rows: *rows,
2477                cols: *cols,
2478                elements: elements.iter().map(|expr| self.id(expr)).collect(),
2479            },
2480            DagNodeKind::Component { input, index } => {
2481                let input = self.id(input);
2482                ExprNode::Component {
2483                    input,
2484                    index: *index,
2485                }
2486            }
2487            DagNodeKind::MatrixElement { input, row, col } => {
2488                let input = self.id(input);
2489                ExprNode::MatrixElement {
2490                    input,
2491                    row: *row,
2492                    col: *col,
2493                }
2494            }
2495            DagNodeKind::MatMul { lhs, rhs } => {
2496                let lhs = self.id(lhs);
2497                let rhs = self.id(rhs);
2498                ExprNode::MatMul { lhs, rhs }
2499            }
2500            DagNodeKind::MatVec { matrix, vector } => {
2501                let matrix = self.id(matrix);
2502                let vector = self.id(vector);
2503                ExprNode::MatVec { matrix, vector }
2504            }
2505            DagNodeKind::Dot { lhs, rhs } => {
2506                let lhs = self.id(lhs);
2507                let rhs = self.id(rhs);
2508                ExprNode::Dot { lhs, rhs }
2509            }
2510            DagNodeKind::Solve { matrix, rhs } => {
2511                let matrix = self.id(matrix);
2512                let rhs = self.id(rhs);
2513                ExprNode::Solve { matrix, rhs }
2514            }
2515        }
2516    }
2517}
2518
2519fn source_kind(kind: &DagNodeKind) -> ExprSourceKind {
2520    match kind {
2521        DagNodeKind::RealConst(_) | DagNodeKind::ComplexConst(_) => ExprSourceKind::Const,
2522        DagNodeKind::ScalarParam(_) => ExprSourceKind::Param,
2523        DagNodeKind::EventScalar(_) | DagNodeKind::EventP4Component { .. } => ExprSourceKind::Event,
2524        DagNodeKind::Unary { .. } => ExprSourceKind::Unary,
2525        DagNodeKind::Binary { .. } => ExprSourceKind::Binary,
2526        DagNodeKind::Complex { .. } => ExprSourceKind::Complex,
2527        DagNodeKind::Vector { .. } | DagNodeKind::Component { .. } | DagNodeKind::Dot { .. } => {
2528            ExprSourceKind::Vector
2529        }
2530        DagNodeKind::Matrix { .. } | DagNodeKind::MatrixElement { .. } => ExprSourceKind::Matrix,
2531        DagNodeKind::MatMul { .. } | DagNodeKind::MatVec { .. } | DagNodeKind::Solve { .. } => {
2532            ExprSourceKind::LinearAlgebra
2533        }
2534    }
2535}
2536
2537#[cfg(test)]
2538mod tests {
2539    use super::*;
2540    use crate::parameter;
2541
2542    #[test]
2543    fn builds_target_syntax_without_layout_or_context() {
2544        let model = (Complex64::I * parameter!("y", initial : 1.0, bounds : (0.0, 2.0))
2545            + parameter!("x"))
2546        .norm_sqr();
2547
2548        let graph = model.to_graph();
2549        assert!(matches!(
2550            graph.node(graph.root()),
2551            Some(ExprNode::Unary {
2552                op: UnaryOp::NormSqr,
2553                ..
2554            })
2555        ));
2556    }
2557
2558    #[test]
2559    fn parameter_nodes_store_specs_but_do_not_make_layouts() {
2560        let graph = Expr::from(parameter!("x", initial: 1.0)).to_graph();
2561        assert!(matches!(
2562            graph.node(graph.root()),
2563            Some(ExprNode::ScalarParam(spec)) if spec.name() == "x"
2564        ));
2565    }
2566
2567    #[test]
2568    fn complex_constructor_builds_expression_node() {
2569        let graph = complex(parameter!("re"), parameter!("im")).to_graph();
2570
2571        assert!(matches!(
2572            graph.node(graph.root()),
2573            Some(ExprNode::Complex { .. })
2574        ));
2575    }
2576
2577    #[test]
2578    fn polar_complex_lowers_to_expression_graph() {
2579        let graph = polar_complex(parameter!("mag"), parameter!("phase")).to_graph();
2580
2581        assert!(graph.nodes().iter().any(|node| matches!(
2582            node,
2583            ExprNode::Unary {
2584                op: UnaryOp::Exp,
2585                ..
2586            }
2587        )));
2588    }
2589
2590    #[test]
2591    fn metadata_survives_graph_construction() {
2592        let graph = event_scalar("mass")
2593            .named("event mass")
2594            .tagged("data")
2595            .tagged("data")
2596            .to_graph();
2597        let metadata = graph.metadata(graph.root()).unwrap();
2598        assert_eq!(metadata.name(), Some("event mass"));
2599        assert_eq!(metadata.tags(), &[Arc::from("data")]);
2600        assert!(metadata.has_tag("data"));
2601    }
2602
2603    #[test]
2604    fn parameter_updates_rewrite_all_same_named_occurrences() {
2605        let expression = Expr::from(parameter!("x")) + Expr::from(parameter!("x"));
2606        let graph = expression
2607            .to_graph()
2608            .with_parameters([(
2609                String::from("x"),
2610                ParameterUpdate {
2611                    state: Some(ParamState::Fixed(2.0)),
2612                    ..Default::default()
2613                },
2614            )])
2615            .unwrap();
2616
2617        assert_eq!(
2618            graph
2619                .nodes()
2620                .iter()
2621                .filter_map(|node| match node {
2622                    ExprNode::ScalarParam(parameter) => Some(parameter),
2623                    _ => None,
2624                })
2625                .count(),
2626            2
2627        );
2628        assert!(graph.nodes().iter().all(|node| {
2629            !matches!(node, ExprNode::ScalarParam(parameter) if parameter.name() == "x" && parameter.is_free())
2630        }));
2631    }
2632
2633    #[test]
2634    fn parameter_updates_reject_duplicates_and_unknown_names_without_mutation() {
2635        let graph = Expr::from(parameter!("x")).to_graph();
2636        let original = graph.to_string();
2637        assert!(matches!(
2638            graph.with_parameters([
2639                ("x", ParameterUpdate::default()),
2640                ("x", ParameterUpdate::default()),
2641            ]),
2642            Err(ParamError::DuplicateName(name)) if name == "x"
2643        ));
2644        assert!(matches!(
2645            graph.with_parameters([("missing", ParameterUpdate::default())]),
2646            Err(ParamError::UnknownName(name)) if name == "missing"
2647        ));
2648        assert_eq!(graph.to_string(), original);
2649    }
2650
2651    #[test]
2652    fn expressions_round_trip_through_serde_with_metadata() {
2653        let expression = ((parameter!("x", initial: 1.0) + 2.0).named("offset")
2654            * event_scalar("mass").tagged("data"))
2655        .tagged("model");
2656        let encoded = serde_json::to_string(&expression).unwrap();
2657        let decoded: Expr = serde_json::from_str(&encoded).unwrap();
2658
2659        assert_eq!(
2660            serde_json::to_value(expression.to_graph()).unwrap(),
2661            serde_json::to_value(decoded.to_graph()).unwrap()
2662        );
2663    }
2664
2665    #[test]
2666    fn display_formats_graph_as_labeled_tree() {
2667        let graph = ((parameter!("x") + 1.0).named("offset") * event_scalar("mass").tagged("data"))
2668            .to_graph();
2669        let display = graph.display_tree().to_string();
2670
2671        assert!(display.starts_with("ExprGraph(root=#"));
2672        assert!(display.contains("Binary(Mul)"));
2673        assert!(display.contains("┣ lhs:"));
2674        assert!(display.contains("┗ rhs:"));
2675        assert!(display.contains("Binary(Add) name=\"offset\""));
2676        assert!(display.contains("ScalarParam(x)"));
2677        assert!(display.contains("RealConst(1)"));
2678        assert!(display.contains("EventScalar(mass) tags=[data]"));
2679    }
2680
2681    #[test]
2682    fn display_formats_graph_as_expression() {
2683        let costheta = Expr::from(parameter!("costheta"));
2684        let phi = event_scalar("phi");
2685        let p = Expr::from(parameter!("p"));
2686        let phase = Expr::from(7.0) * Complex64::I;
2687        let graph =
2688            (((costheta.powi(2) * phi.sin()) - 5.2).norm_sqr() * p.conj() - phase.exp()).to_graph();
2689
2690        assert_eq!(
2691            graph.to_string(),
2692            "|costheta^2 * sin(phi) - 5.2|^2 * conj(p) - exp(7 * i)"
2693        );
2694    }
2695
2696    #[test]
2697    fn display_parenthesizes_when_precedence_requires_it() {
2698        let a = Expr::from(parameter!("a"));
2699        let b = Expr::from(parameter!("b"));
2700        let c = Expr::from(parameter!("c"));
2701
2702        assert_eq!(
2703            (a.clone() * (b.clone() + c.clone())).to_graph().to_string(),
2704            "a * (b + c)"
2705        );
2706        assert_eq!(
2707            (a.clone() - (b.clone() - c.clone())).to_graph().to_string(),
2708            "a - (b - c)"
2709        );
2710        assert_eq!(((a / b) / c).to_graph().to_string(), "a / b / c");
2711    }
2712
2713    #[test]
2714    fn display_rounds_tiny_float_representation_noise() {
2715        let metadata = ExprMetadata::new(ExprSourceKind::Const);
2716        let graph = ExprGraph::from_parts(
2717            ExprId::from_index(2),
2718            vec![
2719                ExprNode::RealConst(2.9999999999999996),
2720                ExprNode::ComplexConst(Complex64::new(0.30000000000000004, 1.9999999999999998)),
2721                ExprNode::Binary {
2722                    op: BinaryOp::Add,
2723                    lhs: ExprId::from_index(0),
2724                    rhs: ExprId::from_index(1),
2725                },
2726            ],
2727            vec![metadata.clone(), metadata.clone(), metadata],
2728        )
2729        .unwrap();
2730
2731        assert_eq!(graph.to_string(), "3 + 0.3 + 2i");
2732        assert!(graph.display_tree().to_string().contains("RealConst(3)"));
2733        assert!(
2734            graph
2735                .display_tree()
2736                .to_string()
2737                .contains("ComplexConst(0.3 + 2i)")
2738        );
2739    }
2740
2741    #[test]
2742    fn display_formats_p4_components_and_atan2() {
2743        let expr = atan2(
2744            event_p4_component("ks1", P4Component::Py),
2745            event_p4_component("ks1", P4Component::Px),
2746        );
2747
2748        assert_eq!(expr.to_graph().to_string(), "atan2(ks1.py, ks1.px)");
2749    }
2750
2751    #[test]
2752    fn graph_from_parts_validates_structure() {
2753        let metadata = ExprMetadata::new(ExprSourceKind::Const);
2754        let graph = ExprGraph::from_parts(
2755            ExprId::from_index(1),
2756            vec![
2757                ExprNode::RealConst(1.0),
2758                ExprNode::Unary {
2759                    op: UnaryOp::Neg,
2760                    input: ExprId::from_index(0),
2761                },
2762            ],
2763            vec![metadata.clone(), metadata.clone()],
2764        )
2765        .unwrap();
2766        assert!(matches!(
2767            graph.node(graph.root()),
2768            Some(ExprNode::Unary {
2769                op: UnaryOp::Neg,
2770                ..
2771            })
2772        ));
2773
2774        let err = ExprGraph::from_parts(
2775            ExprId::from_index(0),
2776            vec![ExprNode::RealConst(1.0)],
2777            Vec::new(),
2778        )
2779        .unwrap_err();
2780        assert!(matches!(err, ExprGraphError::MetadataLength { .. }));
2781
2782        let err = ExprGraph::from_parts(
2783            ExprId::from_index(0),
2784            vec![ExprNode::Unary {
2785                op: UnaryOp::Neg,
2786                input: ExprId::from_index(0),
2787            }],
2788            vec![metadata],
2789        )
2790        .unwrap_err();
2791        assert!(matches!(err, ExprGraphError::InvalidChildOrder { .. }));
2792    }
2793
2794    #[test]
2795    fn graph_preserves_unsimplified_expression_shape() {
2796        let graph = (parameter!("x") + 0.0).to_graph();
2797        assert!(matches!(
2798            graph.node(graph.root()),
2799            Some(ExprNode::Binary {
2800                op: BinaryOp::Add,
2801                ..
2802            })
2803        ));
2804    }
2805
2806    #[test]
2807    fn graph_preserves_written_operand_order_for_commutative_ops() {
2808        let left_param = (parameter!("x") + 1.0).to_graph();
2809        assert!(matches!(
2810            left_param.node(left_param.root()),
2811            Some(ExprNode::Binary {
2812                op: BinaryOp::Add,
2813                lhs,
2814                rhs
2815            }) if matches!(left_param.node(*lhs), Some(ExprNode::ScalarParam(parameter)) if parameter.name() == "x")
2816                && matches!(left_param.node(*rhs), Some(ExprNode::RealConst(1.0)))
2817        ));
2818
2819        let right_param = (1.0 + parameter!("x")).to_graph();
2820        assert!(matches!(
2821            right_param.node(right_param.root()),
2822            Some(ExprNode::Binary {
2823                op: BinaryOp::Add,
2824                lhs,
2825                rhs
2826            }) if matches!(right_param.node(*lhs), Some(ExprNode::RealConst(1.0)))
2827                && matches!(right_param.node(*rhs), Some(ExprNode::ScalarParam(parameter)) if parameter.name() == "x")
2828        ));
2829    }
2830
2831    #[test]
2832    fn represents_kmatrix_style_solve_graph() {
2833        let beta = vector([
2834            complex(parameter!("b0_re"), parameter!("b0_im")),
2835            complex(parameter!("b1_re"), parameter!("b1_im")),
2836        ]);
2837        let a = matrix([
2838            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)],
2839            [Complex64::new(0.0, -1.0), Complex64::new(1.0, 0.0)],
2840        ]);
2841        let graph = solve(a, beta).component(0).to_graph();
2842
2843        assert!(
2844            graph
2845                .nodes()
2846                .iter()
2847                .any(|node| matches!(node, ExprNode::Solve { .. }))
2848        );
2849    }
2850
2851    #[test]
2852    fn graph_builder_preserves_shared_dag_nodes() {
2853        let shared = event_scalar("x").sin();
2854        let expression = vector((0..1_000).map(|_| shared.clone()));
2855        let graph = expression.to_graph();
2856
2857        assert_eq!(graph.nodes().len(), 3);
2858        let ExprNode::Vector { elements } = graph.node(graph.root()).unwrap() else {
2859            panic!("root should be a vector");
2860        };
2861        assert!(elements.windows(2).all(|pair| pair[0] == pair[1]));
2862    }
2863
2864    #[test]
2865    fn expression_projection_preserves_occurrence_rebuild_behavior() {
2866        let shared = event_scalar("x").sin();
2867        let projected = (shared.clone() + shared).project_tags(["selected"]);
2868        let graph = projected.to_graph();
2869
2870        assert_eq!(
2871            graph
2872                .nodes()
2873                .iter()
2874                .filter(|node| matches!(
2875                    node,
2876                    ExprNode::Unary {
2877                        op: UnaryOp::Sin,
2878                        ..
2879                    }
2880                ))
2881                .count(),
2882            2
2883        );
2884    }
2885
2886    #[test]
2887    fn iterative_construction_and_projection_handle_deep_expressions() {
2888        let mut expression = event_scalar("x");
2889        for _ in 0..10_000 {
2890            expression = expression.sin();
2891        }
2892
2893        let projected = expression.project_tags(["selected"]);
2894        let graph = projected.to_graph();
2895
2896        assert_eq!(graph.nodes().len(), 10_001);
2897        assert_eq!(
2898            graph.reachable_post_order([graph.root()]).len(),
2899            graph.nodes().len()
2900        );
2901
2902        // Deep `Arc` chains also recurse when their final owner is dropped;
2903        // this test targets traversal behavior rather than destructor policy.
2904        std::mem::forget(expression);
2905        std::mem::forget(projected);
2906    }
2907
2908    #[test]
2909    fn reachable_post_order_preserves_child_order_and_deduplicates_shared_nodes() {
2910        let shared = event_scalar("x").sin();
2911        let graph = (shared.clone() + shared).to_graph();
2912        let order = graph.reachable_post_order([graph.root()]);
2913
2914        assert_eq!(order.len(), graph.nodes().len());
2915        assert_eq!(order.last(), Some(&graph.root()));
2916        for id in order {
2917            for child in graph.node(id).unwrap().children() {
2918                assert!(child.index() < id.index());
2919            }
2920        }
2921    }
2922
2923    #[test]
2924    fn dynamic_matrices_and_shapes_are_checked_eagerly() {
2925        let dynamic = matrix_from_flat(2, 2, [1.0, 2.0, 3.0, 4.0]).unwrap();
2926        assert_eq!(
2927            dynamic.shape().unwrap(),
2928            ExprShape::Matrix { rows: 2, cols: 2 }
2929        );
2930        assert!(matrix_from_flat(2, 2, [1.0, 2.0, 3.0]).is_err());
2931        assert!(matmul(dynamic, matrix([[1.0, 2.0, 3.0]])).shape().is_err());
2932    }
2933
2934    #[test]
2935    fn assignment_operators_build_binary_expression_nodes() {
2936        let mut expr = Expr::from(parameter!("x"));
2937        expr += parameter!("y");
2938        expr -= 1.0;
2939        expr *= Complex64::I;
2940        expr /= Expr::from(parameter!("z"));
2941
2942        let graph = expr.to_graph();
2943        assert!(matches!(
2944            graph.node(graph.root()),
2945            Some(ExprNode::Binary {
2946                op: BinaryOp::Div,
2947                ..
2948            })
2949        ));
2950        assert_eq!(
2951            graph
2952                .nodes()
2953                .iter()
2954                .filter(|node| matches!(node, ExprNode::Binary { .. }))
2955                .count(),
2956            4
2957        );
2958    }
2959
2960    #[test]
2961    fn assignment_operators_accept_borrowed_rhs_values() {
2962        let y = parameter!("y");
2963        let one = 1.0;
2964        let i = Complex64::I;
2965        let z = Expr::from(parameter!("z"));
2966
2967        let mut expr = Expr::from(parameter!("x"));
2968        expr += &y;
2969        expr -= &one;
2970        expr *= &i;
2971        expr /= &z;
2972
2973        let graph = expr.to_graph();
2974        assert!(matches!(
2975            graph.node(graph.root()),
2976            Some(ExprNode::Binary {
2977                op: BinaryOp::Div,
2978                ..
2979            })
2980        ));
2981    }
2982}