alkahest_cas/kernel/expr.rs
1use crate::kernel::domain::Domain;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5/// Opaque index into an [`crate::kernel::ExprPool`]. `Copy` — expressions are values, not owned objects.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct ExprId(pub(crate) u32);
8
9// ---------------------------------------------------------------------------
10// Atom wrappers — rug types lack Hash, so we provide it via string encoding.
11// String encoding is O(n·log n) but correct and simple for v0.1.
12// ---------------------------------------------------------------------------
13
14/// Arbitrary-precision integer atom.
15#[derive(Debug, Clone)]
16pub struct BigInt(pub rug::Integer);
17
18impl PartialEq for BigInt {
19 fn eq(&self, other: &Self) -> bool {
20 self.0 == other.0
21 }
22}
23impl Eq for BigInt {}
24
25impl Hash for BigInt {
26 fn hash<H: Hasher>(&self, state: &mut H) {
27 self.0.to_string_radix(16).hash(state);
28 }
29}
30
31impl fmt::Display for BigInt {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "{}", self.0)
34 }
35}
36
37/// Arbitrary-precision rational atom. Stored in canonical reduced form by rug.
38#[derive(Debug, Clone)]
39pub struct BigRat(pub rug::Rational);
40
41impl PartialEq for BigRat {
42 fn eq(&self, other: &Self) -> bool {
43 self.0 == other.0
44 }
45}
46impl Eq for BigRat {}
47
48impl Hash for BigRat {
49 fn hash<H: Hasher>(&self, state: &mut H) {
50 self.0.numer().to_string_radix(16).hash(state);
51 self.0.denom().to_string_radix(16).hash(state);
52 }
53}
54
55impl fmt::Display for BigRat {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 if *self.0.denom() == 1 {
58 write!(f, "{}", self.0.numer())
59 } else {
60 write!(f, "{}/{}", self.0.numer(), self.0.denom())
61 }
62 }
63}
64
65/// Arbitrary-precision floating-point atom. `prec` (precision in bits) is
66/// part of structural identity: `Float(1.0, 53) != Float(1.0, 64)`.
67#[derive(Debug, Clone)]
68pub struct BigFloat {
69 pub inner: rug::Float,
70 pub prec: u32,
71}
72
73impl PartialEq for BigFloat {
74 fn eq(&self, other: &Self) -> bool {
75 if self.prec != other.prec {
76 return false;
77 }
78 match (self.inner.is_nan(), other.inner.is_nan()) {
79 (true, true) => true,
80 (false, false) => self.inner == other.inner,
81 _ => false,
82 }
83 }
84}
85impl Eq for BigFloat {}
86
87impl Hash for BigFloat {
88 fn hash<H: Hasher>(&self, state: &mut H) {
89 self.prec.hash(state);
90 if self.inner.is_nan() {
91 "nan".hash(state);
92 } else {
93 // Exact hex representation; None → enough digits for exact round-trip.
94 self.inner.to_string_radix(16, None).hash(state);
95 }
96 }
97}
98
99impl fmt::Display for BigFloat {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 write!(f, "{}", self.inner)
102 }
103}
104
105// ---------------------------------------------------------------------------
106// Predicate — symbolic boolean conditions for Piecewise expressions
107// ---------------------------------------------------------------------------
108
109/// Kind of a symbolic predicate.
110///
111/// Predicates are stored in the intern table as
112/// `ExprData::Predicate { kind, args }`. The `args` field holds the
113/// operands as `ExprId` nodes.
114///
115/// | Kind | Arity | Meaning |
116/// |------|-------|---------|
117/// | `Lt` | 2 | `args[0]` < `args[1]` |
118/// | `Le` | 2 | `args[0]` ≤ `args[1]` |
119/// | `Gt` | 2 | `args[0]` > `args[1]` |
120/// | `Ge` | 2 | `args[0]` ≥ `args[1]` |
121/// | `Eq` | 2 | `args[0]` = `args[1]` (symbolic equality) |
122/// | `Ne` | 2 | `args[0]` ≠ `args[1]` |
123/// | `And`| n | conjunction of n predicate ExprIds |
124/// | `Or` | n | disjunction of n predicate ExprIds |
125/// | `Not`| 1 | negation |
126/// | `True` | 0 | always-true |
127/// | `False`| 0 | always-false |
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub enum PredicateKind {
130 Lt,
131 Le,
132 Gt,
133 Ge,
134 Eq,
135 Ne,
136 And,
137 Or,
138 Not,
139 True,
140 False,
141}
142
143impl fmt::Display for PredicateKind {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 let s = match self {
146 PredicateKind::Lt => "<",
147 PredicateKind::Le => "≤",
148 PredicateKind::Gt => ">",
149 PredicateKind::Ge => "≥",
150 PredicateKind::Eq => "=",
151 PredicateKind::Ne => "≠",
152 PredicateKind::And => "∧",
153 PredicateKind::Or => "∨",
154 PredicateKind::Not => "¬",
155 PredicateKind::True => "True",
156 PredicateKind::False => "False",
157 };
158 write!(f, "{s}")
159 }
160}
161
162// ---------------------------------------------------------------------------
163// Expression data — the structural content stored in the intern table.
164// ---------------------------------------------------------------------------
165
166/// Structural content of an expression node.
167///
168/// All compound nodes hold [`ExprId`] children, not owned sub-trees.
169/// This keeps `ExprData` small and allows sharing via the intern table.
170#[derive(Debug, Clone, PartialEq, Eq, Hash)]
171pub enum ExprData {
172 // Atoms
173 Symbol {
174 name: String,
175 domain: Domain,
176 /// When `false`, this generator does not commute under multiplication; see V3-2.
177 commutative: bool,
178 },
179 Integer(BigInt),
180 Rational(BigRat),
181 Float(BigFloat),
182 // Compound (n-ary for Add/Mul; binary for Pow; variadic for Func)
183 Add(Vec<ExprId>),
184 Mul(Vec<ExprId>),
185 Pow {
186 base: ExprId,
187 exp: ExprId,
188 },
189 Func {
190 name: String,
191 args: Vec<ExprId>,
192 },
193 // PA-9 — symbolic conditionals
194 /// A piecewise expression: evaluates to `value_i` when `cond_i` holds,
195 /// and to `default` when no condition matches.
196 ///
197 /// Conditions are `ExprData::Predicate` nodes stored in the pool.
198 /// Branches are tried in order; the first matching condition wins.
199 Piecewise {
200 branches: Vec<(ExprId /* cond */, ExprId /* value */)>,
201 default: ExprId,
202 },
203 /// A symbolic predicate (boolean condition over symbolic reals).
204 Predicate {
205 kind: PredicateKind,
206 args: Vec<ExprId>,
207 },
208 /// Universal quantification (`∀ var . body`). Used by first-order logic (V3-3).
209 Forall {
210 var: ExprId,
211 body: ExprId,
212 },
213 /// Existential quantification (`∃ var . body`).
214 Exists {
215 var: ExprId,
216 body: ExprId,
217 },
218 /// Landau big-O remainder: `O(arg)` as a symbolic order bound (V2-15 series API).
219 BigO(ExprId),
220}