1mod binary;
8mod sexpr;
9
10use crate::TLExpr;
11
12pub use binary::{from_binary, graph_from_binary, graph_to_binary, to_binary};
13pub use sexpr::{from_sexpr, to_sexpr};
14
15#[derive(Debug, Clone)]
21pub enum ExprSerializeError {
22 IoError(String),
24 FormatError(String),
26 UnknownVariant(String),
28 VersionMismatch { expected: u32, got: u32 },
30 InvalidMagic,
32 TruncatedInput,
34 Utf8Error(String),
36}
37
38impl std::fmt::Display for ExprSerializeError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 Self::IoError(msg) => write!(f, "IO error: {msg}"),
42 Self::FormatError(msg) => write!(f, "Format error: {msg}"),
43 Self::UnknownVariant(v) => write!(f, "Unknown variant: {v}"),
44 Self::VersionMismatch { expected, got } => {
45 write!(f, "Version mismatch: expected {expected}, got {got}")
46 }
47 Self::InvalidMagic => write!(f, "Invalid magic bytes"),
48 Self::TruncatedInput => write!(f, "Truncated input"),
49 Self::Utf8Error(msg) => write!(f, "UTF-8 error: {msg}"),
50 }
51 }
52}
53
54impl std::error::Error for ExprSerializeError {}
55
56#[derive(Debug, Clone, PartialEq)]
62pub enum ExprFormat {
63 SExpr,
65 Binary,
67}
68
69pub(crate) const TLEX_MAGIC: [u8; 4] = [0x54, 0x4C, 0x45, 0x58];
75pub(crate) const TLBX_MAGIC: [u8; 4] = [0x54, 0x4C, 0x42, 0x58];
77pub(crate) const TLGR_MAGIC: [u8; 4] = [0x54, 0x4C, 0x47, 0x52];
79pub(crate) const FORMAT_VER: u32 = 1;
81
82pub(crate) const TAG_PRED: u8 = 0;
87pub(crate) const TAG_AND: u8 = 1;
88pub(crate) const TAG_OR: u8 = 2;
89pub(crate) const TAG_NOT: u8 = 3;
90pub(crate) const TAG_EXISTS: u8 = 4;
91pub(crate) const TAG_FORALL: u8 = 5;
92pub(crate) const TAG_IMPLY: u8 = 6;
93pub(crate) const TAG_SCORE: u8 = 7;
94pub(crate) const TAG_ADD: u8 = 8;
95pub(crate) const TAG_SUB: u8 = 9;
96pub(crate) const TAG_MUL: u8 = 10;
97pub(crate) const TAG_DIV: u8 = 11;
98pub(crate) const TAG_POW: u8 = 12;
99pub(crate) const TAG_MOD: u8 = 13;
100pub(crate) const TAG_MIN: u8 = 14;
101pub(crate) const TAG_MAX: u8 = 15;
102pub(crate) const TAG_ABS: u8 = 16;
103pub(crate) const TAG_FLOOR: u8 = 17;
104pub(crate) const TAG_CEIL: u8 = 18;
105pub(crate) const TAG_ROUND: u8 = 19;
106pub(crate) const TAG_SQRT: u8 = 20;
107pub(crate) const TAG_EXP: u8 = 21;
108pub(crate) const TAG_LOG: u8 = 22;
109pub(crate) const TAG_SIN: u8 = 23;
110pub(crate) const TAG_COS: u8 = 24;
111pub(crate) const TAG_TAN: u8 = 25;
112pub(crate) const TAG_EQ: u8 = 26;
113pub(crate) const TAG_LT: u8 = 27;
114pub(crate) const TAG_GT: u8 = 28;
115pub(crate) const TAG_LTE: u8 = 29;
116pub(crate) const TAG_GTE: u8 = 30;
117pub(crate) const TAG_IF_THEN_ELSE: u8 = 31;
118pub(crate) const TAG_CONSTANT: u8 = 32;
119pub(crate) const TAG_AGGREGATE: u8 = 33;
120pub(crate) const TAG_LET: u8 = 34;
121pub(crate) const TAG_BOX: u8 = 35;
122pub(crate) const TAG_DIAMOND: u8 = 36;
123pub(crate) const TAG_NEXT: u8 = 37;
124pub(crate) const TAG_EVENTUALLY: u8 = 38;
125pub(crate) const TAG_ALWAYS: u8 = 39;
126pub(crate) const TAG_UNTIL: u8 = 40;
127pub(crate) const TAG_TNORM: u8 = 41;
128pub(crate) const TAG_TCONORM: u8 = 42;
129pub(crate) const TAG_FUZZY_NOT: u8 = 43;
130pub(crate) const TAG_FUZZY_IMPLICATION: u8 = 44;
131pub(crate) const TAG_SOFT_EXISTS: u8 = 45;
132pub(crate) const TAG_SOFT_FORALL: u8 = 46;
133pub(crate) const TAG_WEIGHTED_RULE: u8 = 47;
134pub(crate) const TAG_PROBABILISTIC_CHOICE: u8 = 48;
135pub(crate) const TAG_RELEASE: u8 = 49;
136pub(crate) const TAG_WEAK_UNTIL: u8 = 50;
137pub(crate) const TAG_STRONG_RELEASE: u8 = 51;
138pub(crate) const TAG_LAMBDA: u8 = 52;
139pub(crate) const TAG_APPLY: u8 = 53;
140pub(crate) const TAG_SET_MEMBERSHIP: u8 = 54;
141pub(crate) const TAG_SET_UNION: u8 = 55;
142pub(crate) const TAG_SET_INTERSECTION: u8 = 56;
143pub(crate) const TAG_SET_DIFFERENCE: u8 = 57;
144pub(crate) const TAG_SET_CARDINALITY: u8 = 58;
145pub(crate) const TAG_EMPTY_SET: u8 = 59;
146pub(crate) const TAG_SET_COMPREHENSION: u8 = 60;
147pub(crate) const TAG_COUNTING_EXISTS: u8 = 61;
148pub(crate) const TAG_COUNTING_FORALL: u8 = 62;
149pub(crate) const TAG_EXACT_COUNT: u8 = 63;
150pub(crate) const TAG_MAJORITY: u8 = 64;
151pub(crate) const TAG_LEAST_FIXPOINT: u8 = 65;
152pub(crate) const TAG_GREATEST_FIXPOINT: u8 = 66;
153pub(crate) const TAG_NOMINAL: u8 = 67;
154pub(crate) const TAG_AT: u8 = 68;
155pub(crate) const TAG_SOMEWHERE: u8 = 69;
156pub(crate) const TAG_EVERYWHERE: u8 = 70;
157pub(crate) const TAG_ALL_DIFFERENT: u8 = 71;
158pub(crate) const TAG_GLOBAL_CARDINALITY: u8 = 72;
159pub(crate) const TAG_ABDUCIBLE: u8 = 73;
160pub(crate) const TAG_EXPLAIN: u8 = 74;
161pub(crate) const TAG_SYMBOL_LITERAL: u8 = 75;
162pub(crate) const TAG_MATCH: u8 = 76;
163pub(crate) const TAG_PATTERN_CONST_SYMBOL: u8 = 0;
164pub(crate) const TAG_PATTERN_CONST_NUMBER: u8 = 1;
165pub(crate) const TAG_PATTERN_WILDCARD: u8 = 2;
166
167pub(crate) const TERM_TAG_VAR: u8 = 0;
169pub(crate) const TERM_TAG_CONST: u8 = 1;
170pub(crate) const TERM_TAG_TYPED: u8 = 2;
171
172pub(crate) const AGG_COUNT: u8 = 0;
174pub(crate) const AGG_SUM: u8 = 1;
175pub(crate) const AGG_AVERAGE: u8 = 2;
176pub(crate) const AGG_MAX: u8 = 3;
177pub(crate) const AGG_MIN: u8 = 4;
178pub(crate) const AGG_PRODUCT: u8 = 5;
179pub(crate) const AGG_ANY: u8 = 6;
180pub(crate) const AGG_ALL: u8 = 7;
181
182pub(crate) const TNORM_MINIMUM: u8 = 0;
184pub(crate) const TNORM_PRODUCT: u8 = 1;
185pub(crate) const TNORM_LUKASIEWICZ: u8 = 2;
186pub(crate) const TNORM_DRASTIC: u8 = 3;
187pub(crate) const TNORM_NILPOTENT_MINIMUM: u8 = 4;
188pub(crate) const TNORM_HAMACHER: u8 = 5;
189
190pub(crate) const TCONORM_MAXIMUM: u8 = 0;
192pub(crate) const TCONORM_PROBABILISTIC_SUM: u8 = 1;
193pub(crate) const TCONORM_BOUNDED_SUM: u8 = 2;
194pub(crate) const TCONORM_DRASTIC: u8 = 3;
195pub(crate) const TCONORM_NILPOTENT_MAXIMUM: u8 = 4;
196pub(crate) const TCONORM_HAMACHER: u8 = 5;
197
198pub(crate) const FNEG_STANDARD: u8 = 0;
200pub(crate) const FNEG_SUGENO: u8 = 1;
201pub(crate) const FNEG_YAGER: u8 = 2;
202
203pub(crate) const FIMP_GODEL: u8 = 0;
205pub(crate) const FIMP_LUKASIEWICZ: u8 = 1;
206pub(crate) const FIMP_REICHENBACH: u8 = 2;
207pub(crate) const FIMP_KLEENE_DIENES: u8 = 3;
208pub(crate) const FIMP_RESCHER: u8 = 4;
209pub(crate) const FIMP_GOGUEN: u8 = 5;
210
211pub(crate) const OP_EINSUM: u8 = 0;
213pub(crate) const OP_ELEM_UNARY: u8 = 1;
214pub(crate) const OP_ELEM_BINARY: u8 = 2;
215pub(crate) const OP_REDUCE: u8 = 3;
216
217pub fn expr_fingerprint(expr: &TLExpr) -> u64 {
223 let bin = to_binary(expr);
224 fnv1a_hash(&bin[8..]) }
226
227fn fnv1a_hash(data: &[u8]) -> u64 {
229 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
230 const FNV_PRIME: u64 = 0x00000100000001B3;
231 let mut hash = FNV_OFFSET_BASIS;
232 for &byte in data {
233 hash ^= byte as u64;
234 hash = hash.wrapping_mul(FNV_PRIME);
235 }
236 hash
237}
238
239pub fn binary_equal(a: &[u8], b: &[u8]) -> bool {
241 a == b
242}
243
244#[derive(Debug, Clone)]
250pub struct SerializationStats {
251 pub sexpr_bytes: usize,
253 pub binary_bytes: usize,
255 pub compression_ratio: f64,
257 pub node_count: usize,
259 pub max_depth: usize,
261}
262
263impl SerializationStats {
264 pub fn compute(expr: &TLExpr) -> Self {
266 let sexpr_str = to_sexpr(expr);
267 let bin = to_binary(expr);
268 let sexpr_bytes = sexpr_str.len();
269 let binary_bytes = bin.len();
270 let compression_ratio = if binary_bytes > 0 {
271 sexpr_bytes as f64 / binary_bytes as f64
272 } else {
273 0.0
274 };
275 let node_count = count_nodes(expr);
276 let max_depth = compute_depth(expr);
277 Self {
278 sexpr_bytes,
279 binary_bytes,
280 compression_ratio,
281 node_count,
282 max_depth,
283 }
284 }
285
286 pub fn summary(&self) -> String {
288 format!(
289 "sexpr={} bytes, binary={} bytes, ratio={:.2}, nodes={}, depth={}",
290 self.sexpr_bytes,
291 self.binary_bytes,
292 self.compression_ratio,
293 self.node_count,
294 self.max_depth
295 )
296 }
297}
298
299fn count_nodes(expr: &TLExpr) -> usize {
300 let mut count = 1usize;
301 visit_children(expr, &mut |child| count += count_nodes(child));
302 count
303}
304
305fn compute_depth(expr: &TLExpr) -> usize {
306 let mut max_child_depth = 0usize;
307 visit_children(expr, &mut |child| {
308 let d = compute_depth(child);
309 if d > max_child_depth {
310 max_child_depth = d;
311 }
312 });
313 1 + max_child_depth
314}
315
316fn visit_children(expr: &TLExpr, f: &mut impl FnMut(&TLExpr)) {
318 match expr {
319 TLExpr::Pred { .. }
320 | TLExpr::Constant(_)
321 | TLExpr::EmptySet
322 | TLExpr::Nominal { .. }
323 | TLExpr::AllDifferent { .. }
324 | TLExpr::Abducible { .. } => {}
325
326 TLExpr::Not(e)
327 | TLExpr::Score(e)
328 | TLExpr::Abs(e)
329 | TLExpr::Floor(e)
330 | TLExpr::Ceil(e)
331 | TLExpr::Round(e)
332 | TLExpr::Sqrt(e)
333 | TLExpr::Exp(e)
334 | TLExpr::Log(e)
335 | TLExpr::Sin(e)
336 | TLExpr::Cos(e)
337 | TLExpr::Tan(e)
338 | TLExpr::Box(e)
339 | TLExpr::Diamond(e)
340 | TLExpr::Next(e)
341 | TLExpr::Eventually(e)
342 | TLExpr::Always(e) => f(e),
343
344 TLExpr::And(a, b)
345 | TLExpr::Or(a, b)
346 | TLExpr::Imply(a, b)
347 | TLExpr::Add(a, b)
348 | TLExpr::Sub(a, b)
349 | TLExpr::Mul(a, b)
350 | TLExpr::Div(a, b)
351 | TLExpr::Pow(a, b)
352 | TLExpr::Mod(a, b)
353 | TLExpr::Min(a, b)
354 | TLExpr::Max(a, b)
355 | TLExpr::Eq(a, b)
356 | TLExpr::Lt(a, b)
357 | TLExpr::Gt(a, b)
358 | TLExpr::Lte(a, b)
359 | TLExpr::Gte(a, b) => {
360 f(a);
361 f(b);
362 }
363
364 TLExpr::IfThenElse {
365 condition,
366 then_branch,
367 else_branch,
368 } => {
369 f(condition);
370 f(then_branch);
371 f(else_branch);
372 }
373
374 TLExpr::Exists { body, .. }
375 | TLExpr::ForAll { body, .. }
376 | TLExpr::Majority { body, .. }
377 | TLExpr::SetComprehension {
378 condition: body, ..
379 } => f(body),
380
381 TLExpr::Aggregate { body, .. } => f(body),
382
383 TLExpr::Let { value, body, .. } => {
384 f(value);
385 f(body);
386 }
387
388 TLExpr::Until { before, after } | TLExpr::WeakUntil { before, after } => {
389 f(before);
390 f(after);
391 }
392
393 TLExpr::Release { released, releaser } | TLExpr::StrongRelease { released, releaser } => {
394 f(released);
395 f(releaser);
396 }
397
398 TLExpr::TNorm { left, right, .. } | TLExpr::TCoNorm { left, right, .. } => {
399 f(left);
400 f(right);
401 }
402
403 TLExpr::FuzzyNot { expr: e, .. } => f(e),
404 TLExpr::FuzzyImplication {
405 premise,
406 conclusion,
407 ..
408 } => {
409 f(premise);
410 f(conclusion);
411 }
412
413 TLExpr::SoftExists { body, .. } | TLExpr::SoftForAll { body, .. } => f(body),
414 TLExpr::WeightedRule { rule, .. } => f(rule),
415
416 TLExpr::ProbabilisticChoice { alternatives } => {
417 for (_, alt_expr) in alternatives {
418 f(alt_expr);
419 }
420 }
421
422 TLExpr::Lambda { body, .. }
423 | TLExpr::LeastFixpoint { body, .. }
424 | TLExpr::GreatestFixpoint { body, .. } => f(body),
425
426 TLExpr::Apply { function, argument } => {
427 f(function);
428 f(argument);
429 }
430
431 TLExpr::SetMembership { element, set } => {
432 f(element);
433 f(set);
434 }
435 TLExpr::SetUnion { left, right }
436 | TLExpr::SetIntersection { left, right }
437 | TLExpr::SetDifference { left, right } => {
438 f(left);
439 f(right);
440 }
441
442 TLExpr::SetCardinality { set } => f(set),
443
444 TLExpr::CountingExists { body, .. }
445 | TLExpr::CountingForAll { body, .. }
446 | TLExpr::ExactCount { body, .. } => f(body),
447
448 TLExpr::At { formula, .. }
449 | TLExpr::Somewhere { formula }
450 | TLExpr::Everywhere { formula }
451 | TLExpr::Explain { formula } => f(formula),
452
453 TLExpr::GlobalCardinality { values, .. } => {
454 for v in values {
455 f(v);
456 }
457 }
458
459 TLExpr::SymbolLiteral(_) => {}
460
461 TLExpr::Match { scrutinee, arms } => {
462 f(scrutinee);
463 for (_, body) in arms {
464 f(body);
465 }
466 }
467 }
468}
469
470pub fn batch_to_binary(exprs: &[TLExpr]) -> Vec<u8> {
476 let mut w = binary::BinWriter::new();
477 w.write_magic(&TLBX_MAGIC);
478 w.write_u32(FORMAT_VER);
479 w.write_u32(exprs.len() as u32);
480 for expr in exprs {
481 binary::write_expr_bin(expr, &mut w);
482 }
483 w.into_bytes()
484}
485
486pub fn batch_from_binary(bytes: &[u8]) -> Result<Vec<TLExpr>, ExprSerializeError> {
488 let mut r = binary::BinReader::new(bytes);
489 let magic = r.read_magic()?;
490 if magic != TLBX_MAGIC {
491 return Err(ExprSerializeError::InvalidMagic);
492 }
493 let version = r.read_u32()?;
494 if version != FORMAT_VER {
495 return Err(ExprSerializeError::VersionMismatch {
496 expected: FORMAT_VER,
497 got: version,
498 });
499 }
500 let count = r.read_u32()? as usize;
501 let mut exprs = Vec::with_capacity(count);
502 for _ in 0..count {
503 exprs.push(binary::read_expr_bin(&mut r)?);
504 }
505 Ok(exprs)
506}
507
508#[cfg(test)]
513mod tests {
514 use super::*;
515 use crate::{EinsumGraph, EinsumNode, TLExpr, TNormKind, Term};
516
517 fn simple_pred(name: &str, arg: &str) -> TLExpr {
518 TLExpr::Pred {
519 name: name.to_string(),
520 args: vec![Term::Var(arg.to_string())],
521 }
522 }
523
524 #[test]
525 fn test_sexpr_variable() {
526 let expr = simple_pred("P", "x");
527 let s = to_sexpr(&expr);
528 assert!(s.contains("x"));
529 assert!(s.contains("Pred"));
530 }
531
532 #[test]
533 fn test_sexpr_constant() {
534 let expr = TLExpr::Constant(3.15);
535 let s = to_sexpr(&expr);
536 assert!(s.contains("3.15"));
537 assert!(s.contains("Constant"));
538 }
539
540 #[test]
541 fn test_sexpr_not() {
542 let expr = TLExpr::Not(Box::new(simple_pred("P", "x")));
543 let s = to_sexpr(&expr);
544 assert!(s.contains("Not"));
545 assert!(s.contains("Pred"));
546 }
547
548 #[test]
549 fn test_sexpr_and() {
550 let a = simple_pred("P", "x");
551 let b = simple_pred("Q", "y");
552 let expr = TLExpr::And(Box::new(a), Box::new(b));
553 let s = to_sexpr(&expr);
554 assert!(s.contains("And"));
555 assert!(s.contains("\"P\""));
556 assert!(s.contains("\"Q\""));
557 }
558
559 #[test]
560 fn test_sexpr_roundtrip_simple() {
561 let expr = TLExpr::Constant(42.0);
562 let s = to_sexpr(&expr);
563 let parsed = from_sexpr(&s).expect("parse failed");
564 assert_eq!(parsed, expr);
565 }
566
567 #[test]
568 fn test_sexpr_roundtrip_nested() {
569 let inner = TLExpr::And(
570 Box::new(simple_pred("P", "x")),
571 Box::new(TLExpr::Not(Box::new(simple_pred("Q", "y")))),
572 );
573 let expr = TLExpr::ForAll {
574 var: "x".to_string(),
575 domain: "Entity".to_string(),
576 body: Box::new(inner),
577 };
578 let s = to_sexpr(&expr);
579 let parsed = from_sexpr(&s).expect("parse failed");
580 assert_eq!(parsed, expr);
581 }
582
583 #[test]
584 fn test_sexpr_parse_error() {
585 let result = from_sexpr("not valid sexpr )))");
586 assert!(result.is_err());
587 }
588
589 #[test]
590 fn test_binary_roundtrip_variable() {
591 let expr = simple_pred("P", "x");
592 let bin = to_binary(&expr);
593 let parsed = from_binary(&bin).expect("binary parse failed");
594 assert_eq!(parsed, expr);
595 }
596
597 #[test]
598 fn test_binary_roundtrip_constant() {
599 let expr = TLExpr::Constant(2.719);
600 let bin = to_binary(&expr);
601 let parsed = from_binary(&bin).expect("binary parse failed");
602 assert_eq!(parsed, expr);
603 }
604
605 #[test]
606 fn test_binary_roundtrip_not() {
607 let expr = TLExpr::Not(Box::new(TLExpr::Constant(1.0)));
608 let bin = to_binary(&expr);
609 let parsed = from_binary(&bin).expect("binary parse failed");
610 assert_eq!(parsed, expr);
611 }
612
613 #[test]
614 fn test_binary_roundtrip_and() {
615 let expr = TLExpr::And(
616 Box::new(TLExpr::Constant(1.0)),
617 Box::new(TLExpr::Constant(2.0)),
618 );
619 let bin = to_binary(&expr);
620 let parsed = from_binary(&bin).expect("binary parse failed");
621 assert_eq!(parsed, expr);
622 }
623
624 #[test]
625 fn test_binary_roundtrip_nested() {
626 let leaf = simple_pred("leaf", "z");
627 let nested = TLExpr::Imply(
628 Box::new(TLExpr::And(
629 Box::new(TLExpr::Exists {
630 var: "x".to_string(),
631 domain: "D".to_string(),
632 body: Box::new(leaf.clone()),
633 }),
634 Box::new(TLExpr::Not(Box::new(leaf))),
635 )),
636 Box::new(TLExpr::Constant(99.9)),
637 );
638 let bin = to_binary(&nested);
639 let parsed = from_binary(&bin).expect("binary parse failed");
640 assert_eq!(parsed, nested);
641 }
642
643 #[test]
644 fn test_binary_magic_check() {
645 let expr = TLExpr::Constant(1.0);
646 let bin = to_binary(&expr);
647 assert_eq!(&bin[..4], &TLEX_MAGIC);
648 }
649
650 #[test]
651 fn test_binary_invalid_magic() {
652 let data = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x00, 0x00, 0x00];
653 let result = from_binary(&data);
654 assert!(matches!(result, Err(ExprSerializeError::InvalidMagic)));
655 }
656
657 #[test]
658 fn test_binary_truncated() {
659 let data = vec![0x54, 0x4C, 0x45]; let result = from_binary(&data);
661 assert!(matches!(result, Err(ExprSerializeError::TruncatedInput)));
662 }
663
664 #[test]
665 fn test_expr_fingerprint_same() {
666 let a = TLExpr::And(
667 Box::new(TLExpr::Constant(1.0)),
668 Box::new(TLExpr::Constant(2.0)),
669 );
670 let b = TLExpr::And(
671 Box::new(TLExpr::Constant(1.0)),
672 Box::new(TLExpr::Constant(2.0)),
673 );
674 assert_eq!(expr_fingerprint(&a), expr_fingerprint(&b));
675 }
676
677 #[test]
678 fn test_expr_fingerprint_different() {
679 let a = TLExpr::Constant(1.0);
680 let b = TLExpr::Constant(2.0);
681 assert_ne!(expr_fingerprint(&a), expr_fingerprint(&b));
682 }
683
684 #[test]
685 fn test_binary_equal_true() {
686 let expr = TLExpr::Or(
687 Box::new(TLExpr::Constant(1.0)),
688 Box::new(TLExpr::Constant(2.0)),
689 );
690 let a = to_binary(&expr);
691 let b = to_binary(&expr);
692 assert!(binary_equal(&a, &b));
693 }
694
695 #[test]
696 fn test_serialization_stats() {
697 let expr = TLExpr::And(
698 Box::new(simple_pred("P", "x")),
699 Box::new(TLExpr::Not(Box::new(simple_pred("Q", "y")))),
700 );
701 let stats = SerializationStats::compute(&expr);
702 assert!(stats.sexpr_bytes > 0);
703 assert!(stats.binary_bytes > 0);
704 assert!(stats.node_count > 0);
705 assert!(stats.max_depth > 0);
706 let summary = stats.summary();
707 assert!(summary.contains("bytes"));
708 }
709
710 #[test]
711 fn test_batch_roundtrip() {
712 let exprs = vec![
713 TLExpr::Constant(1.0),
714 TLExpr::Not(Box::new(TLExpr::Constant(2.0))),
715 TLExpr::And(
716 Box::new(simple_pred("P", "x")),
717 Box::new(simple_pred("Q", "y")),
718 ),
719 ];
720 let bin = batch_to_binary(&exprs);
721 let parsed = batch_from_binary(&bin).expect("batch parse failed");
722 assert_eq!(parsed.len(), exprs.len());
723 for (a, b) in exprs.iter().zip(parsed.iter()) {
724 assert_eq!(a, b);
725 }
726 }
727
728 #[test]
729 fn test_graph_binary_roundtrip() {
730 let mut graph = EinsumGraph::new();
731 let _a = graph.add_tensor("A");
732 let _b = graph.add_tensor("B");
733 let _c = graph.add_tensor("C");
734 graph
735 .add_node(EinsumNode::einsum("ik,kj->ij", vec![0, 1], vec![2]))
736 .expect("add node failed");
737 graph.add_output(2).expect("add output failed");
738
739 let bin = graph_to_binary(&graph);
740 let parsed = graph_from_binary(&bin).expect("graph parse failed");
741 assert_eq!(parsed.tensors, graph.tensors);
742 assert_eq!(parsed.inputs, graph.inputs);
743 assert_eq!(parsed.outputs, graph.outputs);
744 assert_eq!(parsed.nodes.len(), graph.nodes.len());
745 }
746
747 #[test]
748 fn test_sexpr_roundtrip_empty_set() {
749 let expr = TLExpr::EmptySet;
750 let s = to_sexpr(&expr);
751 let parsed = from_sexpr(&s).expect("parse failed");
752 assert_eq!(parsed, expr);
753 }
754
755 #[test]
756 fn test_binary_roundtrip_lambda() {
757 let expr = TLExpr::Lambda {
758 var: "x".to_string(),
759 var_type: Some("Int".to_string()),
760 body: Box::new(TLExpr::Constant(42.0)),
761 };
762 let bin = to_binary(&expr);
763 let parsed = from_binary(&bin).expect("binary parse failed");
764 assert_eq!(parsed, expr);
765 }
766
767 #[test]
768 fn test_binary_roundtrip_fuzzy() {
769 let expr = TLExpr::TNorm {
770 kind: TNormKind::Lukasiewicz,
771 left: Box::new(TLExpr::Constant(0.5)),
772 right: Box::new(TLExpr::Constant(0.7)),
773 };
774 let bin = to_binary(&expr);
775 let parsed = from_binary(&bin).expect("binary parse failed");
776 assert_eq!(parsed, expr);
777 }
778}