1use crate::kernel::{
2 domain::Domain,
3 expr::{BigFloat, BigInt, BigRat, ExprData, ExprId},
4};
5use std::fmt;
6
7pub const POS_INFINITY_SYMBOL: &str = "\u{221e}";
9
10use hashbrown::HashTable;
38use std::hash::BuildHasher;
39use std::sync::{PoisonError, RwLock};
40
41#[derive(Clone, Copy)]
48struct Slot {
49 id: u32,
50 hash: u32,
51}
52
53#[inline]
57fn slot_hash(h32: u32) -> u64 {
58 let h = u64::from(h32);
59 h | (h << 32)
60}
61
62#[repr(align(128))]
65struct Shard(RwLock<HashTable<Slot>>);
66
67fn shard_count() -> usize {
72 #[cfg(feature = "parallel")]
73 {
74 static SHARDS: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
75 *SHARDS.get_or_init(|| {
76 let cores = std::thread::available_parallelism().map_or(1, |n| n.get());
77 (cores * 4).next_power_of_two().clamp(4, 64)
78 })
79 }
80 #[cfg(not(feature = "parallel"))]
81 {
82 1
83 }
84}
85
86struct PoolIndex {
87 shards: Box<[Shard]>,
88 shard_shift: u32,
91 seed: foldhash::fast::RandomState,
92}
93
94impl PoolIndex {
95 fn new() -> Self {
96 let n = shard_count();
97 debug_assert!(n.is_power_of_two());
98 PoolIndex {
99 shards: (0..n)
100 .map(|_| Shard(RwLock::new(HashTable::new())))
101 .collect(),
102 shard_shift: 64 - n.trailing_zeros(),
103 seed: foldhash::fast::RandomState::default(),
104 }
105 }
106
107 fn get_or_insert_with(
109 &self,
110 nodes: &boxcar::Vec<Node>,
111 data: ExprData,
112 make: impl FnOnce(ExprData) -> ExprId,
113 ) -> ExprId {
114 let hash = self.seed.hash_one(&data);
115 self.get_or_insert_hashed(nodes, hash, data, make)
116 }
117
118 fn get_or_insert_hashed(
125 &self,
126 nodes: &boxcar::Vec<Node>,
127 hash: u64,
128 data: ExprData,
129 make: impl FnOnce(ExprData) -> ExprId,
130 ) -> ExprId {
131 let h32 = hash as u32;
132 let th = slot_hash(h32);
133 let shard = hash.checked_shr(self.shard_shift).unwrap_or(0) as usize;
135 let lock = &self.shards[shard].0;
136 let eq = |s: &Slot| s.hash == h32 && nodes[s.id as usize].data == data;
137
138 if let Some(s) = lock
142 .read()
143 .unwrap_or_else(PoisonError::into_inner)
144 .find(th, eq)
145 {
146 return ExprId(s.id);
147 }
148 let mut table = lock.write().unwrap_or_else(PoisonError::into_inner);
149 if let Some(s) = table.find(th, eq) {
150 return ExprId(s.id);
151 }
152 let id = make(data);
153 table.insert_unique(
154 th,
155 Slot {
156 id: id.0,
157 hash: h32,
158 },
159 |s| slot_hash(s.hash),
160 );
161 id
162 }
163}
164
165struct Node {
175 data: ExprData,
176 mult_commutative: bool,
183 depth: u32,
195 static_domain_fact: bool,
203 non_finite: bool,
208}
209
210pub struct ExprPool {
211 nodes: boxcar::Vec<Node>,
213 index: PoolIndex,
216}
217
218const _: () = {
232 const fn assert_send_sync<T: Send + Sync>() {}
233 assert_send_sync::<ExprPool>();
234 assert_send_sync::<Node>();
235 assert_send_sync::<ExprData>();
236};
237
238pub(crate) const MAX_FLAT_ARITY: usize = 131_072;
247
248impl ExprPool {
249 pub fn new() -> Self {
250 ExprPool {
251 nodes: boxcar::Vec::new(),
252 index: PoolIndex::new(),
253 }
254 }
255
256 pub fn intern(&self, data: ExprData) -> ExprId {
274 let data = match self.canonical_form(data) {
275 Ok(d) => d,
276 Err(id) => return id,
277 };
278 let make = |d: ExprData| {
282 let node = self.make_node(d);
283 ExprId(self.nodes.push(node) as u32)
284 };
285 self.index.get_or_insert_with(&self.nodes, data, make)
286 }
287
288 fn canonical_form(&self, data: ExprData) -> Result<ExprData, ExprId> {
291 match data {
292 ExprData::Rational(r) if *r.0.denom() == 1 => {
293 Ok(ExprData::Integer(BigInt(r.0.into_numer_denom().0)))
294 }
295 ExprData::Float(mut f)
296 if (f.inner.is_zero() || f.inner.is_nan()) && f.inner.is_sign_negative() =>
297 {
298 f.inner.abs_mut();
299 Ok(ExprData::Float(f))
300 }
301 ExprData::Add(args) if args.len() <= 1 => match args.first() {
302 Some(&only) => Err(only),
303 None => Err(self.integer(0_i32)),
304 },
305 ExprData::Mul(args) if args.len() <= 1 => match args.first() {
306 Some(&only) => Err(only),
307 None => Err(self.integer(1_i32)),
308 },
309 other => Ok(other),
310 }
311 }
312
313 fn make_node(&self, data: ExprData) -> Node {
316 let mult_commutative = self.compute_mult_commutative(&data);
317 let depth = self.compute_depth(&data);
318 let static_domain_fact = self.compute_static_domain_fact(&data);
319 let non_finite = self.compute_non_finite(&data);
320 Node {
321 data,
322 mult_commutative,
323 depth,
324 static_domain_fact,
325 non_finite,
326 }
327 }
328
329 fn compute_depth(&self, data: &ExprData) -> u32 {
332 let child = |c: ExprId| self.depth(c);
333 let deepest = match data {
334 ExprData::Symbol { .. }
335 | ExprData::Integer(_)
336 | ExprData::Rational(_)
337 | ExprData::Float(_) => 0,
338 ExprData::Add(args) | ExprData::Mul(args) => {
339 args.iter().copied().map(child).max().unwrap_or(0)
340 }
341 ExprData::Pow { base, exp } => child(*base).max(child(*exp)),
342 ExprData::Func { args, .. } => args.iter().copied().map(child).max().unwrap_or(0),
343 ExprData::Piecewise { branches, default } => branches
344 .iter()
345 .map(|&(c, v)| child(c).max(child(v)))
346 .max()
347 .unwrap_or(0)
348 .max(child(*default)),
349 ExprData::Predicate { args, .. } => args.iter().copied().map(child).max().unwrap_or(0),
350 ExprData::Forall { var, body } | ExprData::Exists { var, body } => {
351 child(*var).max(child(*body))
352 }
353 ExprData::BigO(inner) => child(*inner),
354 ExprData::RootSum { poly, body, .. } => child(*poly).max(child(*body)),
355 };
356 deepest.saturating_add(1)
357 }
358
359 fn compute_mult_commutative(&self, data: &ExprData) -> bool {
362 let child = |c: ExprId| self.is_mult_commutative(c);
363 match data {
364 ExprData::Symbol { commutative, .. } => *commutative,
365 ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Float(_) => true,
366 ExprData::Add(args) | ExprData::Mul(args) => args.iter().copied().all(child),
367 ExprData::Pow { base, exp } => child(*base) && child(*exp),
368 ExprData::Func { args, .. } => args.iter().copied().all(child),
369 ExprData::Piecewise { branches, default } => {
370 branches.iter().all(|&(c, v)| child(c) && child(v)) && child(*default)
371 }
372 ExprData::Predicate { args, .. } => args.iter().copied().all(child),
373 ExprData::Forall { var, body } | ExprData::Exists { var, body } => {
374 child(*var) && child(*body)
375 }
376 ExprData::BigO(inner) => child(*inner),
377 ExprData::RootSum { poly, body, .. } => child(*poly) && child(*body),
378 }
379 }
380
381 pub fn is_mult_commutative(&self, id: ExprId) -> bool {
384 self.node(id).mult_commutative
385 }
386
387 fn compute_static_domain_fact(&self, data: &ExprData) -> bool {
400 let child = |c: ExprId| self.node(c).static_domain_fact;
401 match data {
402 ExprData::Symbol { domain, .. } => {
403 matches!(domain, Domain::Positive | Domain::NonZero)
404 }
405 ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Float(_) => false,
406 ExprData::Add(args)
407 | ExprData::Mul(args)
408 | ExprData::Func { args, .. }
409 | ExprData::Predicate { args, .. } => args.iter().copied().any(child),
410 ExprData::Pow { base, exp } => child(*base) || child(*exp),
411 ExprData::Piecewise { branches, default } => {
412 branches.iter().any(|&(c, v)| child(c) || child(v)) || child(*default)
413 }
414 ExprData::Forall { var, body } | ExprData::Exists { var, body } => {
415 child(*var) || child(*body)
416 }
417 ExprData::BigO(inner) => child(*inner),
418 ExprData::RootSum { poly, var, body } => child(*poly) || child(*var) || child(*body),
419 }
420 }
421
422 fn compute_non_finite(&self, data: &ExprData) -> bool {
425 let child = |c: ExprId| self.node(c).non_finite;
426 match data {
427 ExprData::Symbol { name, .. } => name == POS_INFINITY_SYMBOL,
428 ExprData::Float(f) => !f.inner.is_finite(),
429 ExprData::Integer(_) | ExprData::Rational(_) => false,
430 ExprData::Add(args)
431 | ExprData::Mul(args)
432 | ExprData::Func { args, .. }
433 | ExprData::Predicate { args, .. } => args.iter().copied().any(child),
434 ExprData::Pow { base, exp } => child(*base) || child(*exp),
435 ExprData::Piecewise { branches, default } => {
436 branches.iter().any(|&(c, v)| child(c) || child(v)) || child(*default)
437 }
438 ExprData::Forall { var, body } | ExprData::Exists { var, body } => {
439 child(*var) || child(*body)
440 }
441 ExprData::BigO(inner) => child(*inner),
442 ExprData::RootSum { poly, var, body } => child(*poly) || child(*var) || child(*body),
443 }
444 }
445
446 pub(crate) fn has_non_finite(&self, id: ExprId) -> bool {
449 self.node(id).non_finite
450 }
451
452 pub(crate) fn has_static_domain_fact(&self, id: ExprId) -> bool {
456 self.node(id).static_domain_fact
457 }
458
459 pub fn depth(&self, id: ExprId) -> u32 {
460 self.node(id).depth
461 }
462
463 fn node(&self, id: ExprId) -> &Node {
464 self.nodes
465 .get(id.0 as usize)
466 .expect("ExprPool: ExprId out of range")
467 }
468
469 pub fn with<R, F: FnOnce(&ExprData) -> R>(&self, id: ExprId, f: F) -> R {
471 f(&self.node(id).data)
472 }
473
474 pub fn get(&self, id: ExprId) -> ExprData {
476 self.with(id, |d| d.clone())
477 }
478
479 pub fn len(&self) -> usize {
481 self.nodes.count()
482 }
483
484 pub fn is_empty(&self) -> bool {
485 self.nodes.is_empty()
486 }
487
488 pub fn symbol(&self, name: impl Into<String>, domain: Domain) -> ExprId {
494 self.symbol_commutative(name, domain, true)
495 }
496
497 pub const IMAGINARY_UNIT_NAME: &'static str = "I";
504
505 pub fn imaginary_unit(&self) -> ExprId {
519 self.symbol(Self::IMAGINARY_UNIT_NAME, Domain::Complex)
520 }
521
522 pub fn is_imaginary_unit(&self, id: ExprId) -> bool {
526 self.with(id, |d| {
527 matches!(
528 d,
529 ExprData::Symbol { name, domain, .. }
530 if name == Self::IMAGINARY_UNIT_NAME && *domain == Domain::Complex
531 )
532 })
533 }
534
535 pub fn symbol_commutative(
538 &self,
539 name: impl Into<String>,
540 domain: Domain,
541 commutative: bool,
542 ) -> ExprId {
543 self.intern(ExprData::Symbol {
544 name: name.into(),
545 domain,
546 commutative,
547 })
548 }
549
550 pub fn integer(&self, n: impl Into<rug::Integer>) -> ExprId {
551 self.intern(ExprData::Integer(BigInt(n.into())))
552 }
553
554 pub fn rational(
555 &self,
556 numer: impl Into<rug::Integer>,
557 denom: impl Into<rug::Integer>,
558 ) -> ExprId {
559 let r = rug::Rational::from((numer.into(), denom.into()));
560 self.intern(ExprData::Rational(BigRat(r)))
561 }
562
563 pub fn float(&self, value: f64, prec: u32) -> ExprId {
564 let f = rug::Float::with_val(prec, value);
565 self.intern(ExprData::Float(BigFloat { inner: f, prec }))
566 }
567
568 fn flatten_assoc(&self, args: Vec<ExprId>, want_mul: bool) -> Vec<ExprId> {
589 fn splices(data: &ExprData, want_mul: bool) -> Option<&Vec<ExprId>> {
591 match data {
592 ExprData::Mul(children) if want_mul => Some(children),
593 ExprData::Add(children) if !want_mul => Some(children),
594 _ => None,
595 }
596 }
597
598 if !args
601 .iter()
602 .any(|&a| splices(&self.node(a).data, want_mul).is_some())
603 {
604 return args;
605 }
606
607 let mut out = Vec::with_capacity(args.len() + 4);
639 let mut stack: Vec<ExprId> = args.iter().rev().copied().collect();
640 while let Some(id) = stack.pop() {
641 match splices(&self.node(id).data, want_mul) {
642 Some(children) => {
643 if out.len() + stack.len() + children.len() > MAX_FLAT_ARITY {
644 return args;
650 }
651 stack.extend(children.iter().rev().copied());
652 }
653 None => out.push(id),
654 }
655 }
656 out
657 }
658
659 pub fn add(&self, args: Vec<ExprId>) -> ExprId {
660 let mut args = self.flatten_assoc(args, false);
663 args.sort_unstable();
668 self.intern(ExprData::Add(args))
669 }
670
671 pub fn mul(&self, args: Vec<ExprId>) -> ExprId {
672 let mut args = self.flatten_assoc(args, true);
676 let sort_ok = args
678 .iter()
679 .all(|&a| crate::kernel::expr_props::mult_tree_is_commutative(self, a));
680 if sort_ok {
681 args.sort_unstable();
682 }
683 self.intern(ExprData::Mul(args))
684 }
685
686 pub fn pow(&self, base: ExprId, exp: ExprId) -> ExprId {
687 self.intern(ExprData::Pow { base, exp })
688 }
689
690 pub fn func(&self, name: impl Into<String>, args: Vec<ExprId>) -> ExprId {
697 self.intern(ExprData::Func {
698 name: name.into(),
699 args,
700 })
701 }
702
703 pub fn try_func(
708 &self,
709 name: impl Into<String>,
710 args: Vec<ExprId>,
711 ) -> Result<ExprId, crate::kernel::FuncArityError> {
712 let name = name.into();
713 crate::kernel::FuncArityError::check(&name, args.len())?;
714 Ok(self.func(name, args))
715 }
716
717 pub fn piecewise(&self, branches: Vec<(ExprId, ExprId)>, default: ExprId) -> ExprId {
727 self.intern(ExprData::Piecewise { branches, default })
728 }
729
730 pub fn predicate(&self, kind: crate::kernel::expr::PredicateKind, args: Vec<ExprId>) -> ExprId {
732 self.intern(ExprData::Predicate { kind, args })
733 }
734
735 pub fn pred_lt(&self, a: ExprId, b: ExprId) -> ExprId {
737 self.predicate(crate::kernel::expr::PredicateKind::Lt, vec![a, b])
738 }
739 pub fn pred_le(&self, a: ExprId, b: ExprId) -> ExprId {
740 self.predicate(crate::kernel::expr::PredicateKind::Le, vec![a, b])
741 }
742 pub fn pred_gt(&self, a: ExprId, b: ExprId) -> ExprId {
743 self.predicate(crate::kernel::expr::PredicateKind::Gt, vec![a, b])
744 }
745 pub fn pred_ge(&self, a: ExprId, b: ExprId) -> ExprId {
746 self.predicate(crate::kernel::expr::PredicateKind::Ge, vec![a, b])
747 }
748 pub fn pred_eq(&self, a: ExprId, b: ExprId) -> ExprId {
749 self.predicate(crate::kernel::expr::PredicateKind::Eq, vec![a, b])
750 }
751 pub fn pred_ne(&self, a: ExprId, b: ExprId) -> ExprId {
752 self.predicate(crate::kernel::expr::PredicateKind::Ne, vec![a, b])
753 }
754 pub fn pred_and(&self, args: Vec<ExprId>) -> ExprId {
755 self.predicate(crate::kernel::expr::PredicateKind::And, args)
756 }
757 pub fn pred_or(&self, args: Vec<ExprId>) -> ExprId {
758 self.predicate(crate::kernel::expr::PredicateKind::Or, args)
759 }
760 pub fn pred_not(&self, a: ExprId) -> ExprId {
761 self.predicate(crate::kernel::expr::PredicateKind::Not, vec![a])
762 }
763 pub fn pred_true(&self) -> ExprId {
764 self.predicate(crate::kernel::expr::PredicateKind::True, vec![])
765 }
766 pub fn pred_false(&self) -> ExprId {
767 self.predicate(crate::kernel::expr::PredicateKind::False, vec![])
768 }
769
770 pub fn forall(&self, var: ExprId, body: ExprId) -> ExprId {
773 self.intern(ExprData::Forall { var, body })
774 }
775
776 pub fn exists(&self, var: ExprId, body: ExprId) -> ExprId {
778 self.intern(ExprData::Exists { var, body })
779 }
780
781 pub fn root_sum(&self, poly: ExprId, var: ExprId, body: ExprId) -> ExprId {
783 self.intern(ExprData::RootSum { poly, var, body })
784 }
785
786 pub fn big_o(&self, arg: ExprId) -> ExprId {
788 self.intern(ExprData::BigO(arg))
789 }
790
791 pub fn pos_infinity(&self) -> ExprId {
793 self.symbol(POS_INFINITY_SYMBOL, Domain::Positive)
794 }
795
796 pub fn display(&self, id: ExprId) -> ExprDisplay<'_> {
801 ExprDisplay { id, pool: self }
802 }
803}
804
805impl Default for ExprPool {
806 fn default() -> Self {
807 Self::new()
808 }
809}
810
811pub struct ExprDisplay<'a> {
817 pub id: ExprId,
818 pub pool: &'a ExprPool,
819}
820
821impl fmt::Display for ExprDisplay<'_> {
822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823 let data = self.pool.get(self.id);
824 fmt_data(&data, self.pool, f)
825 }
826}
827
828impl fmt::Debug for ExprDisplay<'_> {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 write!(f, "{}", self)
831 }
832}
833
834fn fmt_pow_atom(id: ExprId, pool: &ExprPool) -> String {
839 let s = pool.display(id).to_string();
840 let needs_parens = match pool.get(id) {
841 ExprData::Symbol { .. } | ExprData::Integer(_) | ExprData::Float(_) => false,
842 ExprData::Func { .. } => false,
843 ExprData::Add(_) | ExprData::Mul(_) => false,
845 ExprData::Rational(_)
846 | ExprData::Pow { .. }
847 | ExprData::Piecewise { .. }
848 | ExprData::Predicate { .. }
849 | ExprData::Forall { .. }
850 | ExprData::Exists { .. }
851 | ExprData::BigO(_)
852 | ExprData::RootSum { .. } => true,
853 };
854 if needs_parens {
855 format!("({s})")
856 } else {
857 s
858 }
859}
860
861fn fmt_pow_base(id: ExprId, pool: &ExprPool) -> String {
872 let s = fmt_pow_atom(id, pool);
873 if s.starts_with('-') {
874 format!("({s})")
875 } else {
876 s
877 }
878}
879
880fn fmt_data(data: &ExprData, pool: &ExprPool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
881 match data {
882 ExprData::Symbol { name, .. } => write!(f, "{}", name),
883 ExprData::Integer(n) => write!(f, "{}", n),
884 ExprData::Rational(r) => write!(f, "{}", r),
885 ExprData::Float(fl) => write!(f, "{}", fl),
886 ExprData::Add(args) => {
887 write!(f, "(")?;
888 for (i, &arg) in args.iter().enumerate() {
889 if i > 0 {
890 write!(f, " + ")?;
891 }
892 write!(f, "{}", pool.display(arg))?;
893 }
894 write!(f, ")")
895 }
896 ExprData::Mul(args) => {
897 write!(f, "(")?;
898 for (i, &arg) in args.iter().enumerate() {
899 if i > 0 {
900 write!(f, " * ")?;
901 }
902 write!(f, "{}", pool.display(arg))?;
903 }
904 write!(f, ")")
905 }
906 ExprData::Pow { base, exp } => {
907 let base_s = fmt_pow_base(*base, pool);
910 let exp_s = fmt_pow_atom(*exp, pool);
911 write!(f, "{base_s}^{exp_s}")
912 }
913 ExprData::Func { name, args } => {
914 write!(f, "{}(", name)?;
915 for (i, &arg) in args.iter().enumerate() {
916 if i > 0 {
917 write!(f, ", ")?;
918 }
919 write!(f, "{}", pool.display(arg))?;
920 }
921 write!(f, ")")
922 }
923 ExprData::Piecewise { branches, default } => {
924 write!(f, "Piecewise(")?;
925 for (i, (cond, val)) in branches.iter().enumerate() {
926 if i > 0 {
927 write!(f, ", ")?;
928 }
929 write!(f, "({}, {})", pool.display(*cond), pool.display(*val))?;
930 }
931 write!(f, "; default={})", pool.display(*default))
932 }
933 ExprData::Predicate { kind, args } => match kind {
934 crate::kernel::expr::PredicateKind::True => write!(f, "True"),
935 crate::kernel::expr::PredicateKind::False => write!(f, "False"),
936 crate::kernel::expr::PredicateKind::Not if args.len() == 1 => {
937 write!(f, "¬({})", pool.display(args[0]))
938 }
939 crate::kernel::expr::PredicateKind::And | crate::kernel::expr::PredicateKind::Or => {
940 write!(f, "(")?;
941 for (i, &arg) in args.iter().enumerate() {
942 if i > 0 {
943 write!(f, " {} ", kind)?;
944 }
945 write!(f, "{}", pool.display(arg))?;
946 }
947 write!(f, ")")
948 }
949 _ if args.len() == 2 => {
950 write!(
951 f,
952 "({} {} {})",
953 pool.display(args[0]),
954 kind,
955 pool.display(args[1])
956 )
957 }
958 _ => {
962 write!(f, "{kind}(")?;
963 for (i, &arg) in args.iter().enumerate() {
964 if i > 0 {
965 write!(f, ", ")?;
966 }
967 write!(f, "{}", pool.display(arg))?;
968 }
969 write!(f, ")")
970 }
971 },
972 ExprData::Forall { var, body } => {
973 write!(f, "∀ {} . {}", pool.display(*var), pool.display(*body))
974 }
975 ExprData::Exists { var, body } => {
976 write!(f, "∃ {} . {}", pool.display(*var), pool.display(*body))
977 }
978 ExprData::BigO(arg) => {
979 write!(f, "O({})", pool.display(*arg))
980 }
981 ExprData::RootSum { poly, var, body } => {
982 write!(
983 f,
984 "RootSum({}, {} . {})",
985 pool.display(*poly),
986 pool.display(*var),
987 pool.display(*body)
988 )
989 }
990 }
991}
992
993#[cfg(test)]
998mod tests {
999 use super::*;
1000 use crate::kernel::domain::Domain;
1001
1002 fn pool() -> ExprPool {
1003 ExprPool::new()
1004 }
1005
1006 #[test]
1007 fn noncommutative_mul_orders_distinct() {
1008 let p = pool();
1009 let a = p.symbol_commutative("A", Domain::Real, false);
1010 let b = p.symbol_commutative("B", Domain::Real, false);
1011 assert_ne!(
1012 p.mul(vec![a, b]),
1013 p.mul(vec![b, a]),
1014 "A*B and B*A must not hash-cons together for NC symbols"
1015 );
1016 }
1017
1018 #[test]
1019 fn symbol_commutative_is_structural() {
1020 let p = pool();
1021 let xc = p.symbol_commutative("x", Domain::Real, true);
1022 let xnc = p.symbol_commutative("x", Domain::Real, false);
1023 assert_ne!(xc, xnc);
1024 }
1025
1026 #[test]
1029 fn symbol_interning() {
1030 let p = pool();
1031 let x1 = p.symbol("x", Domain::Real);
1032 let x2 = p.symbol("x", Domain::Real);
1033 assert_eq!(x1, x2, "same symbol must return same ExprId");
1034 }
1035
1036 #[test]
1037 fn domain_is_structural() {
1038 let p = pool();
1039 let xr = p.symbol("x", Domain::Real);
1040 let xc = p.symbol("x", Domain::Complex);
1041 assert_ne!(xr, xc, "same name but different domain must be distinct");
1042 }
1043
1044 #[test]
1045 fn integer_interning() {
1046 let p = pool();
1047 let a = p.integer(42_i32);
1048 let b = p.integer(42_i32);
1049 let c = p.integer(99_i32);
1050 assert_eq!(a, b);
1051 assert_ne!(a, c);
1052 }
1053
1054 #[test]
1055 fn rational_canonical() {
1056 let p = pool();
1057 let r1 = p.rational(2_i32, 4_i32);
1059 let r2 = p.rational(1_i32, 2_i32);
1060 assert_eq!(r1, r2, "rationals must be reduced to canonical form");
1061 }
1062
1063 #[test]
1065 fn rational_with_unit_denominator_is_the_integer() {
1066 let p = pool();
1067 assert_eq!(p.rational(4_i32, 2_i32), p.integer(2_i32));
1068 assert_eq!(p.rational(-6_i32, 3_i32), p.integer(-2_i32));
1069 assert_eq!(p.rational(0_i32, 5_i32), p.integer(0_i32));
1070 assert!(matches!(
1071 p.get(p.rational(4_i32, 2_i32)),
1072 ExprData::Integer(_)
1073 ));
1074 let raw = p.intern(ExprData::Rational(BigRat(rug::Rational::from(7))));
1076 assert_eq!(raw, p.integer(7_i32));
1077 }
1078
1079 #[test]
1081 fn unary_and_empty_sums_and_products_collapse() {
1082 let p = pool();
1083 let x = p.symbol("x", Domain::Real);
1084 assert_eq!(p.add(vec![x]), x);
1085 assert_eq!(p.mul(vec![x]), x);
1086 assert_eq!(p.add(vec![]), p.integer(0_i32));
1087 assert_eq!(p.mul(vec![]), p.integer(1_i32));
1088 assert_eq!(p.add(vec![p.add(vec![x])]), x);
1089 assert_eq!(p.intern(ExprData::Mul(vec![x])), x);
1090 assert_eq!(p.intern(ExprData::Add(vec![])), p.integer(0_i32));
1091 let a = p.symbol_commutative("A", Domain::Real, false);
1093 assert_eq!(p.mul(vec![a]), a);
1094 assert_eq!(p.display(p.add(vec![])).to_string(), "0");
1095 assert_eq!(p.display(p.mul(vec![])).to_string(), "1");
1096 }
1097
1098 #[test]
1101 fn signed_zero_floats_are_one_node_stored_positive() {
1102 let p = pool();
1103 let neg = p.float(-0.0, 53);
1104 let pos = p.float(0.0, 53);
1105 assert_eq!(neg, pos);
1106 match p.get(neg) {
1107 ExprData::Float(f) => assert!(f.inner.is_zero() && !f.inner.is_sign_negative()),
1108 other => panic!("{other:?}"),
1109 }
1110 assert_eq!(p.display(neg).to_string(), "0.0");
1111 let n1 = p.float(f64::NAN, 53);
1112 let n2 = p.float(-f64::NAN, 53);
1113 assert_eq!(n1, n2);
1114 }
1115
1116 #[test]
1118 fn float_display_is_never_integer_shaped() {
1119 let p = pool();
1120 for (v, prec) in [
1121 (0.0, 53),
1122 (1.0, 53),
1123 (2.0_f64.powi(60), 53),
1124 (3.0, 64),
1125 (0.0, 200),
1126 ] {
1127 let s = p.display(p.float(v, prec)).to_string();
1128 assert!(s.contains(['.', 'e']), "{v} at {prec} printed as {s}");
1129 match p.get(crate::parse::parse(&s, &p, &mut Default::default()).unwrap()) {
1130 ExprData::Float(_) => {}
1131 other => panic!("{s} re-parsed as {other:?}"),
1132 }
1133 }
1134 }
1135
1136 #[test]
1137 fn float_precision_is_structural() {
1138 let p = pool();
1139 let f53 = p.float(1.0, 53);
1140 let f64_ = p.float(1.0, 64);
1141 assert_ne!(
1142 f53, f64_,
1143 "same value but different precision is a different expr"
1144 );
1145 }
1146
1147 #[test]
1150 fn subexpression_sharing() {
1151 let p = pool();
1152 let x = p.symbol("x", Domain::Real);
1153 let two = p.integer(2_i32);
1154
1155 let xsq1 = p.pow(x, two);
1157 let xsq2 = p.pow(x, two);
1158 assert_eq!(xsq1, xsq2);
1159
1160 assert_eq!(p.len(), 3);
1162 }
1163
1164 #[test]
1165 fn add_interning() {
1166 let p = pool();
1167 let x = p.symbol("x", Domain::Real);
1168 let y = p.symbol("y", Domain::Real);
1169 let s1 = p.add(vec![x, y]);
1170 let s2 = p.add(vec![x, y]);
1171 assert_eq!(s1, s2);
1172 }
1173
1174 #[test]
1175 fn arg_order_is_canonical() {
1176 let p = pool();
1179 let x = p.symbol("x", Domain::Real);
1180 let y = p.symbol("y", Domain::Real);
1181 let s1 = p.add(vec![x, y]);
1182 let s2 = p.add(vec![y, x]);
1183 assert_eq!(s1, s2, "a+b and b+a must be the same expression after PA-3");
1184 let m1 = p.mul(vec![x, y]);
1185 let m2 = p.mul(vec![y, x]);
1186 assert_eq!(m1, m2, "a*b and b*a must be the same expression after PA-3");
1187 }
1188
1189 #[test]
1190 fn func_interning() {
1191 let p = pool();
1192 let x = p.symbol("x", Domain::Real);
1193 let s1 = p.func("sin", vec![x]);
1194 let s2 = p.func("sin", vec![x]);
1195 let c1 = p.func("cos", vec![x]);
1196 assert_eq!(s1, s2);
1197 assert_ne!(s1, c1);
1198 }
1199
1200 fn nary_args(p: &ExprPool, id: ExprId) -> Vec<ExprId> {
1204 p.with(id, |d| match d {
1205 ExprData::Add(a) | ExprData::Mul(a) => a.clone(),
1206 other => panic!("expected an Add or Mul, got {other:?}"),
1207 })
1208 }
1209
1210 #[test]
1211 fn mul_splices_nested_children() {
1212 let p = pool();
1213 let x = p.symbol("x", Domain::Real);
1214 let y = p.symbol("y", Domain::Real);
1215 let z = p.symbol("z", Domain::Real);
1216
1217 let flat = p.mul(vec![x, y, z]);
1218 let left = p.mul(vec![p.mul(vec![x, y]), z]); let right = p.mul(vec![x, p.mul(vec![y, z])]); assert_eq!(left, flat, "(x*y)*z must intern as the flat x*y*z");
1222 assert_eq!(right, flat, "x*(y*z) must intern as the flat x*y*z");
1223 assert_eq!(nary_args(&p, flat).len(), 3);
1224 }
1225
1226 #[test]
1227 fn add_splices_nested_children() {
1228 let p = pool();
1229 let x = p.symbol("x", Domain::Real);
1230 let y = p.symbol("y", Domain::Real);
1231 let z = p.symbol("z", Domain::Real);
1232
1233 let flat = p.add(vec![x, y, z]);
1234 assert_eq!(p.add(vec![p.add(vec![x, y]), z]), flat);
1235 assert_eq!(p.add(vec![x, p.add(vec![y, z])]), flat);
1236 assert_eq!(nary_args(&p, flat).len(), 3);
1237 }
1238
1239 #[test]
1242 fn splicing_does_not_cross_operators() {
1243 let p = pool();
1244 let x = p.symbol("x", Domain::Real);
1245 let y = p.symbol("y", Domain::Real);
1246 let z = p.symbol("z", Domain::Real);
1247
1248 let sum = p.add(vec![x, y]);
1249 let prod = p.mul(vec![sum, z]); assert_eq!(nary_args(&p, prod).len(), 2);
1251 assert_ne!(prod, p.mul(vec![x, y, z]));
1252
1253 let prod2 = p.mul(vec![x, y]);
1254 let sum2 = p.add(vec![prod2, z]); assert_eq!(nary_args(&p, sum2).len(), 2);
1256 assert_ne!(sum2, p.add(vec![x, y, z]));
1257 }
1258
1259 #[test]
1262 fn splicing_preserves_order_for_noncommutative_generators() {
1263 let p = pool();
1264 let a = p.symbol_commutative("A", Domain::Real, false);
1265 let b = p.symbol_commutative("B", Domain::Real, false);
1266 let c = p.symbol_commutative("C", Domain::Real, false);
1267
1268 let flat = p.mul(vec![a, b, c]);
1269 assert_eq!(p.mul(vec![p.mul(vec![a, b]), c]), flat);
1270 assert_eq!(p.mul(vec![a, p.mul(vec![b, c])]), flat);
1271 assert_eq!(p.display(flat).to_string(), "(A * B * C)");
1272
1273 assert_ne!(flat, p.mul(vec![b, a, c]));
1275 }
1276
1277 #[test]
1282 fn splicing_a_deeply_nested_chain_does_not_recurse() {
1283 const N: i64 = 50_000;
1284 let p = pool();
1285 let x = p.symbol("x", Domain::Real);
1286 let mut acc = x;
1287 for i in 0..N {
1288 let k = p.integer(i);
1289 acc = p.intern(ExprData::Add(vec![acc, k]));
1291 }
1292 assert_eq!(p.depth(acc), N as u32 + 1);
1293
1294 let flat = p.add(vec![acc]);
1295 assert_eq!(p.depth(flat), 2);
1296 assert_eq!(nary_args(&p, flat).len(), N as usize + 1);
1297 }
1298
1299 #[test]
1304 fn empty_same_operator_children_are_the_identity_literals() {
1305 let p = pool();
1306 let x = p.symbol("x", Domain::Real);
1307 let empty_mul = p.mul(vec![]);
1308 assert_eq!(empty_mul, p.integer(1_i32));
1309 assert_eq!(p.mul(vec![x, empty_mul]), p.mul(vec![x, p.integer(1_i32)]));
1310 let empty_add = p.add(vec![]);
1311 assert_eq!(empty_add, p.integer(0_i32));
1312 assert_eq!(p.add(vec![x, empty_add]), p.add(vec![x, p.integer(0_i32)]));
1313 }
1314
1315 #[test]
1318 fn flattening_improves_hash_consing() {
1319 let p = pool();
1320 let x = p.symbol("x", Domain::Real);
1321 let y = p.symbol("y", Domain::Real);
1322 let z = p.symbol("z", Domain::Real);
1323 let before = p.len();
1324 p.mul(vec![p.mul(vec![x, y]), z]);
1325 p.mul(vec![x, p.mul(vec![y, z])]);
1326 p.mul(vec![x, y, z]);
1327 assert_eq!(p.len() - before, 3);
1329 }
1330
1331 #[test]
1334 fn display_symbol() {
1335 let p = pool();
1336 let x = p.symbol("x", Domain::Real);
1337 assert_eq!(p.display(x).to_string(), "x");
1338 }
1339
1340 #[test]
1341 fn display_integer() {
1342 let p = pool();
1343 let n = p.integer(42_i32);
1344 assert_eq!(p.display(n).to_string(), "42");
1345 }
1346
1347 #[test]
1348 fn display_pow() {
1349 let p = pool();
1350 let x = p.symbol("x", Domain::Real);
1351 let two = p.integer(2_i32);
1352 let xsq = p.pow(x, two);
1353 assert_eq!(p.display(xsq).to_string(), "x^2");
1354 }
1355
1356 #[test]
1357 fn display_add() {
1358 let p = pool();
1359 let x = p.symbol("x", Domain::Real);
1360 let y = p.symbol("y", Domain::Real);
1361 let s = p.add(vec![x, y]);
1362 assert_eq!(p.display(s).to_string(), "(x + y)");
1363 }
1364
1365 #[test]
1366 fn display_func() {
1367 let p = pool();
1368 let x = p.symbol("x", Domain::Real);
1369 let s = p.func("sin", vec![x]);
1370 assert_eq!(p.display(s).to_string(), "sin(x)");
1371 }
1372
1373 #[test]
1374 fn display_nested() {
1375 let p = pool();
1376 let x = p.symbol("x", Domain::Real);
1377 let two = p.integer(2_i32);
1378 let xsq = p.pow(x, two);
1379 let one = p.integer(1_i32);
1380 let expr = p.add(vec![xsq, one]);
1381 assert_eq!(p.display(expr).to_string(), "(x^2 + 1)");
1382 }
1383
1384 #[test]
1387 fn display_negative_pow_base_is_parenthesised() {
1388 let p = pool();
1389 let n = p.symbol("n", Domain::Real);
1390 let m1 = p.integer(-1_i32);
1391 assert_eq!(p.display(p.pow(m1, n)).to_string(), "(-1)^n");
1392 let m2 = p.integer(-2_i32);
1393 assert_eq!(p.display(p.pow(m2, n)).to_string(), "(-2)^n");
1394 let half = p.rational(-1, 2);
1395 assert_eq!(p.display(p.pow(half, n)).to_string(), "(-1/2)^n");
1396 let m3 = p.integer(-3_i32);
1398 assert_eq!(p.display(p.pow(m2, m3)).to_string(), "(-2)^-3");
1399 let m16 = p.integer(-16_i32);
1402 let prod = p.mul(vec![m16, p.pow(m2, n)]);
1403 assert_eq!(p.display(prod).to_string(), "((-2)^n * -16)");
1404 }
1405
1406 #[test]
1408 fn display_positive_pow_base_is_bare() {
1409 let p = pool();
1410 let n = p.symbol("n", Domain::Real);
1411 let two = p.integer(2_i32);
1412 assert_eq!(p.display(p.pow(two, n)).to_string(), "2^n");
1413 let x = p.symbol("x", Domain::Real);
1414 assert_eq!(p.display(p.pow(x, n)).to_string(), "x^n");
1415 }
1416
1417 fn assert_send_sync<T: Send + Sync>() {}
1420
1421 #[test]
1422 fn pool_is_send_sync() {
1423 assert_send_sync::<ExprPool>();
1424 }
1425}
1426
1427#[cfg(test)]
1428mod flat_arity_cap_tests {
1429 use super::*;
1430 use crate::kernel::Domain;
1431
1432 #[test]
1437 fn self_combination_terminates_instead_of_doubling_forever() {
1438 let pool = ExprPool::new();
1439 let x = pool.symbol("x", Domain::Real);
1440 let mut e = pool.mul(vec![x, pool.integer(2_i32)]);
1446 for _ in 0..22 {
1447 e = pool.mul(vec![e, e]);
1448 }
1449 let width = match &pool.get(e) {
1453 ExprData::Mul(children) => children.len(),
1454 _ => 1,
1455 };
1456 assert!(
1457 width <= MAX_FLAT_ARITY,
1458 "a declined splice must leave a bounded node, got {width} children"
1459 );
1460 }
1461
1462 #[test]
1465 fn a_large_honest_sum_still_flattens() {
1466 let pool = ExprPool::new();
1467 let x = pool.symbol("x", Domain::Real);
1468 let _ = x;
1469 let terms: Vec<_> = (0..60_000).map(|i| pool.integer(i)).collect();
1470 let sum = pool.add(terms);
1471 let ExprData::Add(children) = &pool.get(sum) else {
1472 panic!("expected a flat Add");
1473 };
1474 assert!(
1475 children.len() >= 59_000,
1476 "a 60k-term sum must stay flat, got {} children",
1477 children.len()
1478 );
1479 }
1480
1481 #[test]
1484 fn ordinary_nesting_still_splices() {
1485 let pool = ExprPool::new();
1486 let x = pool.symbol("x", Domain::Real);
1487 let y = pool.symbol("y", Domain::Real);
1488 let z = pool.symbol("z", Domain::Real);
1489 let inner = pool.mul(vec![x, y]);
1490 assert_eq!(pool.mul(vec![inner, z]), pool.mul(vec![x, y, z]));
1491 }
1492}
1493
1494#[cfg(test)]
1495mod intern_index_tests {
1496 use super::*;
1497 use crate::kernel::Domain;
1498 use std::sync::Barrier;
1499
1500 #[test]
1503 fn concurrent_intern_of_the_same_values_agrees() {
1504 const THREADS: usize = 8;
1505 const N: i64 = 2_000;
1506 let pool = ExprPool::new();
1507 let x = pool.symbol("x", Domain::Real);
1508 let before = pool.len();
1509 let barrier = Barrier::new(THREADS);
1510 let per_thread: Vec<Vec<ExprId>> = std::thread::scope(|s| {
1511 let handles: Vec<_> = (0..THREADS)
1512 .map(|t| {
1513 let (pool, barrier) = (&pool, &barrier);
1514 s.spawn(move || {
1515 barrier.wait();
1516 let stride = [1, 3, 7, 9, 11, 13, 17, 19][t];
1519 let mut ids = vec![ExprId(u32::MAX); 4 * N as usize];
1520 for j in 0..N {
1521 let k = (j * stride) % N;
1522 let i = k as usize;
1523 ids[4 * i] = pool.integer(k - N / 2);
1524 ids[4 * i + 1] = pool.rational(k, 7);
1525 ids[4 * i + 2] = pool.float(k as f64 * 0.25, 53);
1526 ids[4 * i + 3] = pool.add(vec![x, pool.integer(k + N)]);
1527 }
1528 ids
1529 })
1530 })
1531 .collect();
1532 handles.into_iter().map(|h| h.join().unwrap()).collect()
1533 });
1534 for ids in &per_thread[1..] {
1535 assert_eq!(ids, &per_thread[0], "threads disagree on an id");
1536 }
1537 let serial = ExprPool::new();
1541 let sx = serial.symbol("x", Domain::Real);
1542 let serial_before = serial.len();
1543 for k in 0..N {
1544 serial.integer(k - N / 2);
1545 serial.rational(k, 7);
1546 serial.float(k as f64 * 0.25, 53);
1547 serial.add(vec![sx, serial.integer(k + N)]);
1548 }
1549 assert_eq!(
1550 pool.len() - before,
1551 serial.len() - serial_before,
1552 "a racing intern created a duplicate node"
1553 );
1554 for k in 0..N {
1556 let i = 4 * k as usize;
1557 assert_eq!(
1558 pool.get(per_thread[0][i]),
1559 ExprData::Integer(crate::kernel::expr::BigInt(rug::Integer::from(k - N / 2)))
1560 );
1561 }
1562 }
1563
1564 fn intern_with_hash(pool: &ExprPool, hash: u64, data: ExprData) -> ExprId {
1567 pool.index
1568 .get_or_insert_hashed(&pool.nodes, hash, data, |d| {
1569 ExprId(pool.nodes.push(pool.make_node(d)) as u32)
1570 })
1571 }
1572
1573 fn int(n: i64) -> ExprData {
1574 ExprData::Integer(crate::kernel::expr::BigInt(rug::Integer::from(n)))
1575 }
1576
1577 #[test]
1581 fn forced_hash_collision_keeps_nodes_distinct() {
1582 let pool = ExprPool::new();
1583 let ids: Vec<ExprId> = (0..200)
1586 .map(|k| intern_with_hash(&pool, 7, int(k)))
1587 .collect();
1588 let distinct: std::collections::HashSet<ExprId> = ids.iter().copied().collect();
1589 assert_eq!(distinct.len(), 200, "a hash collision merged two nodes");
1590 for (k, &id) in ids.iter().enumerate() {
1591 assert_eq!(pool.get(id), int(k as i64));
1592 assert_eq!(intern_with_hash(&pool, 7, int(k as i64)), id);
1594 }
1595 let a = intern_with_hash(&pool, 0x0000_0000_dead_beef, int(-1));
1597 let b = intern_with_hash(&pool, 0xffff_ffff_dead_beef, int(-2));
1598 assert_ne!(a, b);
1599 assert_eq!(pool.len(), 202);
1600 }
1601
1602 fn assert_hash_consed(pool: &ExprPool) {
1605 let mut seen = std::collections::HashMap::new();
1606 for i in 0..pool.len() {
1607 let id = ExprId(i as u32);
1608 let data = pool.get(id);
1609 assert_eq!(
1610 pool.intern(data.clone()),
1611 id,
1612 "node {i} re-interned elsewhere"
1613 );
1614 if let Some(prev) = seen.insert(data, id) {
1615 panic!("nodes {prev:?} and {id:?} hold equal data");
1616 }
1617 }
1618 }
1619
1620 #[test]
1625 fn concurrent_compound_interning_stays_hash_consed() {
1626 const THREADS: usize = 8;
1627 const N: i64 = 1_500;
1628 let pool = ExprPool::new();
1629 let barrier = Barrier::new(THREADS);
1630 let per_thread: Vec<Vec<ExprId>> = std::thread::scope(|s| {
1631 let handles: Vec<_> = (0..THREADS)
1632 .map(|t| {
1633 let (pool, barrier) = (&pool, &barrier);
1634 s.spawn(move || {
1635 let x = pool.symbol("x", Domain::Real);
1636 let y = pool.symbol("y", Domain::Real);
1637 barrier.wait();
1638 let mut out = vec![ExprId(u32::MAX); N as usize];
1639 let order: Vec<i64> = if t % 2 == 0 {
1643 (0..N).collect()
1644 } else {
1645 (0..N).rev().collect()
1646 };
1647 for k in order {
1648 let c = pool.integer(k);
1649 let p = pool.pow(x, c);
1650 let m = pool.mul(vec![c, p, y]);
1651 let f = pool.func("sin", vec![m]);
1652 out[k as usize] = pool.add(vec![f, p, pool.rational(k, 3)]);
1653 }
1654 out
1655 })
1656 })
1657 .collect();
1658 handles.into_iter().map(|h| h.join().unwrap()).collect()
1659 });
1660 for ids in &per_thread[1..] {
1661 assert_eq!(ids, &per_thread[0], "threads disagree on an id");
1662 }
1663 assert_hash_consed(&pool);
1664 }
1665
1666 #[test]
1669 fn hits_during_concurrent_growth_are_stable() {
1670 let pool = ExprPool::new();
1671 let x = pool.symbol("x", Domain::Real);
1672 let base: Vec<ExprId> = (0..500i64)
1673 .map(|k| pool.add(vec![x, pool.integer(k)]))
1674 .collect();
1675 std::thread::scope(|s| {
1676 for w in 0..4i64 {
1677 let pool = &pool;
1678 s.spawn(move || {
1679 for k in 0..20_000i64 {
1680 pool.integer(1_000_000 + w * 20_000 + k);
1681 }
1682 });
1683 }
1684 for _ in 0..4 {
1685 let (pool, base) = (&pool, &base);
1686 s.spawn(move || {
1687 for round in 0..40 {
1688 for (k, &id) in base.iter().enumerate() {
1689 let again = pool.add(vec![x, pool.integer(k as i64)]);
1690 assert_eq!(again, id, "round {round}: id moved");
1691 }
1692 }
1693 });
1694 }
1695 });
1696 assert_eq!(pool.len(), 1 + 2 * 500 + 80_000);
1697 assert_hash_consed(&pool);
1698 }
1699
1700 #[test]
1705 fn a_panicking_intern_does_not_wedge_the_pool() {
1706 const H: u64 = 0x1234_5678_9abc_def0;
1707 let pool = ExprPool::new();
1708 let x = pool.symbol("x", Domain::Real);
1709 let bogus = ExprData::Pow {
1710 base: x,
1711 exp: ExprId(1_000_000),
1712 };
1713 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1714 intern_with_hash(&pool, H, bogus);
1715 }));
1716 assert!(r.is_err());
1717 let before = pool.len();
1718 let a = intern_with_hash(&pool, H, int(5));
1720 assert_eq!(intern_with_hash(&pool, H, int(5)), a);
1721 assert_eq!(pool.get(a), int(5));
1722 assert_eq!(pool.len(), before + 1);
1723 }
1724
1725 #[test]
1727 fn ids_survive_index_growth() {
1728 let pool = ExprPool::new();
1729 let ids: Vec<ExprId> = (0..50_000i64).map(|k| pool.integer(k)).collect();
1730 for (k, id) in ids.iter().enumerate() {
1731 assert_eq!(pool.integer(k as i64), *id);
1732 }
1733 assert_eq!(pool.len(), 50_000);
1734 }
1735}
1736
1737#[cfg(test)]
1738mod intern_timing {
1739 use super::*;
1743 use crate::kernel::expr::{BigFloat, BigInt};
1744 use rug::ops::Pow;
1745 use std::hash::BuildHasher;
1746 use std::time::Instant;
1747
1748 fn per_op(label: &str, iters: u32, mut f: impl FnMut()) {
1749 for _ in 0..(iters / 10).max(1) {
1750 f();
1751 }
1752 let t = Instant::now();
1753 for _ in 0..iters {
1754 f();
1755 }
1756 let ns = t.elapsed().as_nanos() as f64 / f64::from(iters);
1757 println!("{label:<40} {ns:>12.1} ns/op");
1758 }
1759
1760 #[test]
1761 #[ignore]
1762 fn number_atom_hash_and_intern_timings() {
1763 let rs = std::collections::hash_map::RandomState::new();
1764 let small = BigInt(rug::Integer::from(123_456_789));
1765 let big = BigInt(rug::Integer::from(3).pow(2000));
1766 let huge = rug::Integer::from(1) << 100_000u32;
1767 let flt = BigFloat {
1768 inner: rug::Float::with_val(53, 1.1),
1769 prec: 53,
1770 };
1771 per_op("hash BigInt small", 1_000_000, || {
1772 std::hint::black_box(rs.hash_one(std::hint::black_box(&small)));
1773 });
1774 per_op("hash BigInt 3^2000", 100_000, || {
1775 std::hint::black_box(rs.hash_one(std::hint::black_box(&big)));
1776 });
1777 per_op("hash BigFloat 53-bit", 1_000_000, || {
1778 std::hint::black_box(rs.hash_one(std::hint::black_box(&flt)));
1779 });
1780
1781 let p = ExprPool::new();
1782 for k in 0..1000i64 {
1783 p.integer(k);
1784 }
1785 let mut k = 0i64;
1786 per_op("pool.integer(k) hit", 1_000_000, || {
1787 k = (k + 1) % 1000;
1788 std::hint::black_box(p.integer(k));
1789 });
1790 per_op("pool.rational(1,3) hit", 1_000_000, || {
1791 std::hint::black_box(p.rational(1, 3));
1792 });
1793 per_op("pool.float(1.1,53) hit", 1_000_000, || {
1794 std::hint::black_box(p.float(1.1, 53));
1795 });
1796 p.integer(huge.clone());
1797 per_op("pool.integer(2^100000) hit", 10_000, || {
1798 std::hint::black_box(p.integer(huge.clone()));
1799 });
1800
1801 let fresh = ExprPool::new();
1802 let mut n = 0i64;
1803 per_op("pool.integer miss (fresh values)", 1_000_000, || {
1804 n += 1;
1805 std::hint::black_box(fresh.integer(n));
1806 });
1807 let x = fresh.symbol("x", crate::kernel::Domain::Real);
1808 let mut m = 0i64;
1809 per_op("pool.add([x, k]) miss", 500_000, || {
1810 m += 1;
1811 let c = fresh.integer(m);
1812 std::hint::black_box(fresh.add(vec![x, c]));
1813 });
1814 }
1815}