Skip to main content

rssn_advanced/dag/
metadata.rs

1//! Per-node metadata for DAG entries.
2//!
3//! Each DAG node carries metadata that describes its algebraic properties:
4//! structural hash (via `rapidhash`), numeric coefficient, arity, and
5//! commutativity flags.
6
7use bincode_next::{Decode, Encode};
8
9/// Hash value computed by `rapidhash` for structural deduplication.
10///
11/// Two nodes with the same `NodeHash` are structurally identical
12/// (modulo hash collisions, which are verified by full structural comparison).
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
14pub struct NodeHash(pub(crate) u64);
15
16impl NodeHash {
17    /// The hash value for an empty / uninitialized node.
18    pub const ZERO: Self = Self(0);
19}
20
21/// Flags that describe algebraic properties of a node.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
23pub struct NodeFlags {
24    bits: u8,
25}
26
27impl NodeFlags {
28    /// No flags set.
29    pub const EMPTY: Self = Self { bits: 0 };
30
31    /// This node's operator is commutative (e.g. `+`, `*`).
32    const COMMUTATIVE: u8 = 0b0000_0001;
33    /// This node's operator is associative.
34    const ASSOCIATIVE: u8 = 0b0000_0010;
35    /// This node has been simplified / is in canonical form.
36    const CANONICAL: u8 = 0b0000_0100;
37
38    /// Creates flags with commutativity set.
39    #[must_use]
40    pub const fn commutative() -> Self {
41        Self {
42            bits: Self::COMMUTATIVE,
43        }
44    }
45
46    /// Creates flags with both commutativity and associativity set.
47    #[must_use]
48    pub const fn commutative_associative() -> Self {
49        Self {
50            bits: Self::COMMUTATIVE | Self::ASSOCIATIVE,
51        }
52    }
53
54    /// Returns `true` if the commutative flag is set.
55    #[must_use]
56    pub const fn is_commutative(self) -> bool {
57        self.bits & Self::COMMUTATIVE != 0
58    }
59
60    /// Returns `true` if the associative flag is set.
61    #[must_use]
62    pub const fn is_associative(self) -> bool {
63        self.bits & Self::ASSOCIATIVE != 0
64    }
65
66    /// Returns `true` if the canonical flag is set.
67    #[must_use]
68    pub const fn is_canonical(self) -> bool {
69        self.bits & Self::CANONICAL != 0
70    }
71
72    /// Sets the canonical flag.
73    #[must_use]
74    pub const fn with_canonical(self) -> Self {
75        Self {
76            bits: self.bits | Self::CANONICAL,
77        }
78    }
79
80    /// Returns the flags with the canonical bit cleared.
81    ///
82    /// Used by the deduplication map to compare structural identity without
83    /// regard to whether a node has been marked canonical at runtime.
84    #[must_use]
85    pub const fn without_canonical(self) -> Self {
86        Self {
87            bits: self.bits & !Self::CANONICAL,
88        }
89    }
90
91    /// Returns the raw bit pattern — used when packing to the wire
92    /// representation in [`crate::dag::packed`].
93    #[must_use]
94    pub const fn bits(self) -> u8 {
95        self.bits
96    }
97
98    /// Reconstructs flags from a raw bit pattern. Unknown bits are
99    /// preserved; the future-proofing matters for forward compatibility
100    /// when newer encodings carry flags this version doesn't know yet.
101    #[must_use]
102    pub const fn from_bits(bits: u8) -> Self {
103        Self { bits }
104    }
105}
106
107/// Metadata attached to every DAG node.
108///
109/// This is stored inline in the `DagNode` and contains all the information
110/// needed for hash-consing, algebraic classification, and simplification.
111///
112/// Note: `arity` was removed — it always equalled `node.children.len()`.
113/// Callers that need the arity call `node.children.len()` directly.
114#[derive(Debug, Clone, PartialEq, Encode, Decode)]
115pub struct NodeMetadata {
116    /// Structural hash for deduplication lookups.
117    pub hash: NodeHash,
118    /// Numeric coefficient (e.g. the `3` in `3*x`).
119    /// Defaults to `1.0` for non-coefficient nodes.
120    pub coefficient: f64,
121    /// Algebraic property flags.
122    pub flags: NodeFlags,
123}
124
125impl NodeMetadata {
126    /// Creates metadata for a leaf node with coefficient 1.
127    #[must_use]
128    pub const fn leaf(hash: NodeHash) -> Self {
129        Self {
130            hash,
131            coefficient: 1.0,
132            flags: NodeFlags::EMPTY,
133        }
134    }
135
136    /// Creates metadata for an operator node with the given flags.
137    #[must_use]
138    pub const fn operator(hash: NodeHash, flags: NodeFlags) -> Self {
139        Self {
140            hash,
141            coefficient: 1.0,
142            flags,
143        }
144    }
145
146    /// Returns a copy with the coefficient set to the given value.
147    #[must_use]
148    pub const fn with_coefficient(mut self, coeff: f64) -> Self {
149        self.coefficient = coeff;
150        self
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn flags_round_trip() {
160        let f = NodeFlags::commutative_associative();
161        assert!(f.is_commutative());
162        assert!(f.is_associative());
163        assert!(!f.is_canonical());
164
165        let f2 = f.with_canonical();
166        assert!(f2.is_canonical());
167        assert!(f2.is_commutative());
168    }
169
170    #[test]
171    fn leaf_metadata() {
172        let m = NodeMetadata::leaf(NodeHash(42));
173        assert_eq!(m.coefficient, 1.0);
174        assert_eq!(m.hash, NodeHash(42));
175    }
176
177    #[test]
178    fn coefficient_override() {
179        let m = NodeMetadata::leaf(NodeHash(1)).with_coefficient(3.5);
180        assert_eq!(m.coefficient, 3.5);
181    }
182}