1mod complex_f64;
9
10use crate::ball::{ArbBall, IntervalEval};
11use crate::kernel::expr::PredicateKind;
12use crate::kernel::{ExprData, ExprId, ExprPool};
13use rug::Rational;
14use std::collections::HashMap;
15use std::fmt;
16
17pub use complex_f64::{eval_complex_f64, ComplexF64};
18
19pub enum EvalMode<'a> {
25 ExactRational(&'a HashMap<ExprId, Rational>),
28 F64(&'a HashMap<ExprId, f64>),
30 Interval(&'a IntervalEval),
32}
33
34#[derive(Clone, Debug, PartialEq)]
36pub enum EvalValue {
37 Rational(Rational),
38 F64(f64),
39 Interval(ArbBall),
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub enum UnsupportedReason {
45 UnboundSymbol {
46 symbol: ExprId,
47 },
48 FloatLiteralInExactMode,
49 NonIntegerExponent,
50 ZeroToNegativePower,
51 UnsupportedFunction {
52 name: String,
53 },
54 UnsupportedExpression {
55 kind: &'static str,
56 },
57 InvalidPredicateArity {
58 kind: PredicateKind,
59 expected: usize,
60 actual: usize,
61 },
62 IndeterminatePredicate,
63 NonFiniteResult,
64 IntervalEvaluationFailed,
65}
66
67impl UnsupportedReason {
68 pub const fn code(&self) -> &'static str {
70 match self {
71 Self::UnboundSymbol { .. } => "E-EVAL-001",
72 Self::FloatLiteralInExactMode => "E-EVAL-002",
73 Self::NonIntegerExponent => "E-EVAL-003",
74 Self::ZeroToNegativePower => "E-EVAL-004",
75 Self::UnsupportedFunction { .. } => "E-EVAL-005",
76 Self::UnsupportedExpression { .. } => "E-EVAL-006",
77 Self::InvalidPredicateArity { .. } => "E-EVAL-007",
78 Self::IndeterminatePredicate => "E-EVAL-008",
79 Self::NonFiniteResult => "E-EVAL-009",
80 Self::IntervalEvaluationFailed => "E-EVAL-010",
81 }
82 }
83
84 pub fn agent_code(&self) -> &'static str {
87 match self {
88 Self::UnsupportedExpression { kind: "branch_cut" } => "E-EVAL-011",
89 other => other.code(),
90 }
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct EvalError {
98 pub reason: UnsupportedReason,
99}
100
101impl fmt::Display for EvalError {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "evaluation unsupported: {:?}", self.reason)
104 }
105}
106
107impl std::error::Error for EvalError {}
108
109pub fn evaluate(expr: ExprId, pool: &ExprPool, mode: EvalMode<'_>) -> Result<EvalValue, EvalError> {
111 match mode {
112 EvalMode::ExactRational(bindings) => {
113 eval_exact_rational(expr, pool, bindings).map(EvalValue::Rational)
114 }
115 EvalMode::F64(bindings) => eval_f64(expr, pool, bindings).map(EvalValue::F64),
116 EvalMode::Interval(eval) => eval
117 .eval(expr, pool)
118 .map(EvalValue::Interval)
119 .ok_or(error(UnsupportedReason::IntervalEvaluationFailed)),
120 }
121}
122
123pub fn eval_exact_rational(
125 expr: ExprId,
126 pool: &ExprPool,
127 bindings: &HashMap<ExprId, Rational>,
128) -> Result<Rational, EvalError> {
129 eval_rational_node(expr, pool, bindings)
130}
131
132pub fn eval_f64(
134 expr: ExprId,
135 pool: &ExprPool,
136 bindings: &HashMap<ExprId, f64>,
137) -> Result<f64, EvalError> {
138 let result = eval_f64_node(expr, pool, bindings)?;
139 if result.is_finite() {
140 Ok(result)
141 } else {
142 Err(error(UnsupportedReason::NonFiniteResult))
143 }
144}
145
146pub fn eval_interval(
148 expr: ExprId,
149 pool: &ExprPool,
150 eval: &IntervalEval,
151) -> Result<ArbBall, EvalError> {
152 evaluate(expr, pool, EvalMode::Interval(eval)).map(|value| match value {
153 EvalValue::Interval(ball) => ball,
154 _ => unreachable!("interval mode always returns an interval"),
155 })
156}
157
158fn error(reason: UnsupportedReason) -> EvalError {
159 EvalError { reason }
160}
161
162fn eval_rational_node(
163 expr: ExprId,
164 pool: &ExprPool,
165 bindings: &HashMap<ExprId, Rational>,
166) -> Result<Rational, EvalError> {
167 match pool.get(expr) {
168 ExprData::Integer(n) => Ok(Rational::from(n.0.clone())),
169 ExprData::Rational(r) => Ok(r.0.clone()),
170 ExprData::Float(_) => Err(error(UnsupportedReason::FloatLiteralInExactMode)),
171 ExprData::Symbol { .. } => bindings
172 .get(&expr)
173 .cloned()
174 .ok_or(error(UnsupportedReason::UnboundSymbol { symbol: expr })),
175 ExprData::Add(args) => {
176 let mut sum = Rational::from(0);
177 for arg in args {
178 sum += eval_rational_node(arg, pool, bindings)?;
179 }
180 Ok(sum)
181 }
182 ExprData::Mul(args) => {
183 let mut product = Rational::from(1);
184 for arg in args {
185 product *= eval_rational_node(arg, pool, bindings)?;
186 }
187 Ok(product)
188 }
189 ExprData::Pow { base, exp } => {
190 let base = eval_rational_node(base, pool, bindings)?;
191 let exponent = integer_exponent(exp, pool)?;
192 rational_pow(base, exponent)
193 }
194 ExprData::Piecewise { branches, default } => {
195 for (condition, value) in branches {
196 if eval_rational_predicate(condition, pool, bindings)? {
197 return eval_rational_node(value, pool, bindings);
198 }
199 }
200 eval_rational_node(default, pool, bindings)
201 }
202 ExprData::Predicate { .. } => Ok(Rational::from(eval_rational_predicate(
203 expr, pool, bindings,
204 )? as i32)),
205 ExprData::Func { name, .. } => Err(error(UnsupportedReason::UnsupportedFunction {
206 name: name.clone(),
207 })),
208 other => Err(error(UnsupportedReason::UnsupportedExpression {
209 kind: expr_kind(&other),
210 })),
211 }
212}
213
214fn integer_exponent(expr: ExprId, pool: &ExprPool) -> Result<i64, EvalError> {
215 match pool.get(expr) {
216 ExprData::Integer(n) => {
217 n.0.to_i64()
218 .ok_or(error(UnsupportedReason::NonIntegerExponent))
219 }
220 ExprData::Rational(r) if *r.0.denom() == 1 => {
221 r.0.numer()
222 .to_i64()
223 .ok_or(error(UnsupportedReason::NonIntegerExponent))
224 }
225 _ => Err(error(UnsupportedReason::NonIntegerExponent)),
226 }
227}
228
229fn rational_pow(mut base: Rational, exponent: i64) -> Result<Rational, EvalError> {
230 if exponent < 0 && base == 0 {
231 return Err(error(UnsupportedReason::ZeroToNegativePower));
232 }
233 let mut result = Rational::from(1);
234 let mut power = exponent.unsigned_abs();
235 while power != 0 {
236 if power & 1 == 1 {
237 result *= &base;
238 }
239 power >>= 1;
240 if power != 0 {
241 base *= base.clone();
242 }
243 }
244 if exponent < 0 {
245 Ok(Rational::from(1) / result)
246 } else {
247 Ok(result)
248 }
249}
250
251fn eval_rational_predicate(
252 expr: ExprId,
253 pool: &ExprPool,
254 bindings: &HashMap<ExprId, Rational>,
255) -> Result<bool, EvalError> {
256 let ExprData::Predicate { kind, args } = pool.get(expr) else {
257 return Err(error(UnsupportedReason::IndeterminatePredicate));
258 };
259 match kind {
260 PredicateKind::True => check_arity(&kind, &args, 0).map(|_| true),
261 PredicateKind::False => check_arity(&kind, &args, 0).map(|_| false),
262 PredicateKind::Not => Ok(!eval_rational_predicate(
263 predicate_arg(&kind, &args, 0)?,
264 pool,
265 bindings,
266 )?),
267 PredicateKind::And => {
268 for &arg in &args {
269 if !eval_rational_predicate(arg, pool, bindings)? {
270 return Ok(false);
271 }
272 }
273 Ok(true)
274 }
275 PredicateKind::Or => {
276 for &arg in &args {
277 if eval_rational_predicate(arg, pool, bindings)? {
278 return Ok(true);
279 }
280 }
281 Ok(false)
282 }
283 PredicateKind::Lt
284 | PredicateKind::Le
285 | PredicateKind::Gt
286 | PredicateKind::Ge
287 | PredicateKind::Eq
288 | PredicateKind::Ne => {
289 check_arity(&kind, &args, 2)?;
290 let lhs = eval_rational_node(args[0], pool, bindings)?;
291 let rhs = eval_rational_node(args[1], pool, bindings)?;
292 Ok(match kind {
293 PredicateKind::Lt => lhs < rhs,
294 PredicateKind::Le => lhs <= rhs,
295 PredicateKind::Gt => lhs > rhs,
296 PredicateKind::Ge => lhs >= rhs,
297 PredicateKind::Eq => lhs == rhs,
298 PredicateKind::Ne => lhs != rhs,
299 _ => unreachable!(),
300 })
301 }
302 }
303}
304
305fn eval_f64_node(
306 expr: ExprId,
307 pool: &ExprPool,
308 bindings: &HashMap<ExprId, f64>,
309) -> Result<f64, EvalError> {
310 match pool.get(expr) {
311 ExprData::Integer(n) => Ok(n.0.to_f64()),
312 ExprData::Rational(r) => Ok(r.0.to_f64()),
313 ExprData::Float(f) => Ok(f.inner.to_f64()),
314 ExprData::Symbol { .. } => bindings
315 .get(&expr)
316 .copied()
317 .ok_or(error(UnsupportedReason::UnboundSymbol { symbol: expr })),
318 ExprData::Add(args) => {
319 let mut sum = 0.0;
320 for arg in args {
321 sum += eval_f64_node(arg, pool, bindings)?;
322 }
323 Ok(sum)
324 }
325 ExprData::Mul(args) => {
326 let mut product = 1.0;
327 for arg in args {
328 product *= eval_f64_node(arg, pool, bindings)?;
329 }
330 Ok(product)
331 }
332 ExprData::Pow { base, exp } => {
333 Ok(eval_f64_node(base, pool, bindings)?.powf(eval_f64_node(exp, pool, bindings)?))
334 }
335 ExprData::Func { name, args } if args.len() == 1 => {
336 let arg = eval_f64_node(args[0], pool, bindings)?;
337 match name.as_str() {
338 "sin" => Ok(arg.sin()),
339 "cos" => Ok(arg.cos()),
340 "exp" => Ok(arg.exp()),
341 "log" => Ok(arg.ln()),
342 "sqrt" => Ok(arg.sqrt()),
343 _ => Err(error(UnsupportedReason::UnsupportedFunction {
344 name: name.clone(),
345 })),
346 }
347 }
348 ExprData::Func { name, .. } => Err(error(UnsupportedReason::UnsupportedFunction {
349 name: name.clone(),
350 })),
351 ExprData::Piecewise { branches, default } => {
352 for (condition, value) in branches {
353 if eval_f64_predicate(condition, pool, bindings)? {
354 return eval_f64_node(value, pool, bindings);
355 }
356 }
357 eval_f64_node(default, pool, bindings)
358 }
359 ExprData::Predicate { .. } => Ok(eval_f64_predicate(expr, pool, bindings)? as i32 as f64),
360 other => Err(error(UnsupportedReason::UnsupportedExpression {
361 kind: expr_kind(&other),
362 })),
363 }
364}
365
366fn eval_f64_predicate(
367 expr: ExprId,
368 pool: &ExprPool,
369 bindings: &HashMap<ExprId, f64>,
370) -> Result<bool, EvalError> {
371 let ExprData::Predicate { kind, args } = pool.get(expr) else {
372 return Err(error(UnsupportedReason::IndeterminatePredicate));
373 };
374 match kind {
375 PredicateKind::True => check_arity(&kind, &args, 0).map(|_| true),
376 PredicateKind::False => check_arity(&kind, &args, 0).map(|_| false),
377 PredicateKind::Not => Ok(!eval_f64_predicate(
378 predicate_arg(&kind, &args, 0)?,
379 pool,
380 bindings,
381 )?),
382 PredicateKind::And => {
383 for &arg in &args {
384 if !eval_f64_predicate(arg, pool, bindings)? {
385 return Ok(false);
386 }
387 }
388 Ok(true)
389 }
390 PredicateKind::Or => {
391 for &arg in &args {
392 if eval_f64_predicate(arg, pool, bindings)? {
393 return Ok(true);
394 }
395 }
396 Ok(false)
397 }
398 PredicateKind::Lt
399 | PredicateKind::Le
400 | PredicateKind::Gt
401 | PredicateKind::Ge
402 | PredicateKind::Eq
403 | PredicateKind::Ne => {
404 check_arity(&kind, &args, 2)?;
405 let lhs = eval_f64_node(args[0], pool, bindings)?;
406 let rhs = eval_f64_node(args[1], pool, bindings)?;
407 Ok(match kind {
408 PredicateKind::Lt => lhs < rhs,
409 PredicateKind::Le => lhs <= rhs,
410 PredicateKind::Gt => lhs > rhs,
411 PredicateKind::Ge => lhs >= rhs,
412 PredicateKind::Eq => lhs == rhs,
413 PredicateKind::Ne => lhs != rhs,
414 _ => unreachable!(),
415 })
416 }
417 }
418}
419
420pub(crate) fn check_arity(
421 kind: &PredicateKind,
422 args: &[ExprId],
423 expected: usize,
424) -> Result<(), EvalError> {
425 if args.len() == expected {
426 Ok(())
427 } else {
428 Err(error(UnsupportedReason::InvalidPredicateArity {
429 kind: kind.clone(),
430 expected,
431 actual: args.len(),
432 }))
433 }
434}
435
436pub(crate) fn predicate_arg(
437 kind: &PredicateKind,
438 args: &[ExprId],
439 index: usize,
440) -> Result<ExprId, EvalError> {
441 check_arity(kind, args, 1)?;
442 Ok(args[index])
443}
444
445pub(crate) fn expr_kind(expr: &ExprData) -> &'static str {
446 match expr {
447 ExprData::Forall { .. } => "Forall",
448 ExprData::Exists { .. } => "Exists",
449 ExprData::BigO(_) => "BigO",
450 ExprData::RootSum { .. } => "RootSum",
451 _ => "unknown",
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::ball::ArbBall;
459 use crate::kernel::Domain;
460
461 #[test]
462 fn exact_rational_mode_preserves_fractional_result() {
463 let pool = ExprPool::new();
464 let x = pool.symbol("x", Domain::Real);
465 let expr = pool.add(vec![pool.rational(1, 3), x]);
466 let bindings = HashMap::from([(x, Rational::from((1, 6)))]);
467
468 assert_eq!(
469 eval_exact_rational(expr, &pool, &bindings).unwrap(),
470 Rational::from((1, 2))
471 );
472 }
473
474 #[test]
475 fn f64_mode_evaluates_transcendental_function() {
476 let pool = ExprPool::new();
477 let expr = pool.func("sqrt", vec![pool.integer(9_i32)]);
478
479 let result = eval_f64(expr, &pool, &HashMap::new()).unwrap();
480 assert_eq!(result, 3.0);
481 }
482
483 #[test]
484 fn exact_mode_rejects_float_literal_structurally() {
485 let pool = ExprPool::new();
486 let expr = pool.float(0.5, 53);
487
488 assert_eq!(
489 eval_exact_rational(expr, &pool, &HashMap::new())
490 .unwrap_err()
491 .reason,
492 UnsupportedReason::FloatLiteralInExactMode
493 );
494 }
495
496 #[test]
497 fn facade_interval_mode_refuses_threshold_spanning_piecewise() {
498 let pool = ExprPool::new();
499 let x = pool.symbol("x", Domain::Real);
500 let expr = pool.piecewise(
501 vec![(pool.pred_ge(x, pool.integer(0_i32)), pool.integer(1_i32))],
502 pool.integer(-1_i32),
503 );
504 let mut interval = IntervalEval::new(128);
505 interval.bind(x, ArbBall::from_midpoint_radius(0.0, 1.0, 128));
506
507 assert_eq!(
508 eval_interval(expr, &pool, &interval).unwrap_err().reason,
509 UnsupportedReason::IntervalEvaluationFailed
510 );
511 }
512}