1use crate::dim::Assertion;
2use crate::internal::*;
3
4use super::{DimLike, sym::*};
5use itertools::Itertools;
6use num_integer::Integer;
7use num_traits::{AsPrimitive, PrimInt, Zero};
8use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10use std::fmt::Debug;
11use std::ops::Neg;
12use std::{fmt, ops};
13
14#[derive(Debug)]
15pub enum TooEarly {
16 UndeterminedSymbol(String),
17 Other(String),
18}
19
20impl std::fmt::Display for TooEarly {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 TooEarly::UndeterminedSymbol(s) => write!(f, "Undetermined symbol in expression: {s}"),
24 TooEarly::Other(s) => write!(f, "{s}"),
25 }
26 }
27}
28
29impl std::error::Error for TooEarly {}
30
31macro_rules! b( ($e:expr) => { Box::new($e) } );
32
33#[allow(clippy::derived_hash_with_manual_eq)]
39#[derive(Clone, Eq, Hash, Debug)]
40pub enum TDim {
41 Val(i64),
42 Sym(Symbol),
43 Add(Vec<TDim>),
44 Mul(Vec<TDim>),
45 MulInt(i64, Box<TDim>),
46 Div(Box<TDim>, u64),
47 Broadcast(Vec<TDim>),
48 Min(Vec<TDim>),
49 Max(Vec<TDim>),
50 Ge(Box<TDim>, Box<TDim>),
52 Eq(Box<TDim>, Box<TDim>),
54}
55
56use TDim::*;
57
58fn eq_structural(a: &TDim, b: &TDim) -> bool {
63 match (a, b) {
64 (Val(x), Val(y)) => x == y,
65 (Sym(x), Sym(y)) => x == y,
66 (Add(x), Add(y))
67 | (Mul(x), Mul(y))
68 | (Broadcast(x), Broadcast(y))
69 | (Min(x), Min(y))
70 | (Max(x), Max(y)) => {
71 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| eq_structural(a, b))
72 }
73 (MulInt(p, x), MulInt(q, y)) => p == q && eq_structural(x, y),
74 (Div(x, p), Div(y, q)) => p == q && eq_structural(x, y),
75 (Ge(a, b), Ge(c, d)) | (Eq(a, b), Eq(c, d)) => eq_structural(a, c) && eq_structural(b, d),
76 _ => false,
77 }
78}
79
80std::thread_local! {
86 static EQ_GUARD: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
87}
88
89impl PartialEq for TDim {
90 fn eq(&self, other: &Self) -> bool {
91 if eq_structural(self, other) {
93 return true;
94 }
95 if EQ_GUARD.with(|g| g.get()) {
98 return false;
99 }
100 if matches!(self, Val(_) | Sym(_)) || matches!(other, Val(_) | Sym(_)) {
109 return false;
110 }
111 EQ_GUARD.with(|g| g.set(true));
118 let diff = (self.clone() - other.clone()).simplify();
119 EQ_GUARD.with(|g| g.set(false));
120 matches!(diff, Val(0))
121 }
122}
123
124fn tdim_lexi_order(a: &TDim, b: &TDim) -> Ordering {
125 match (a, b) {
126 (Sym(a), Sym(b)) => a.cmp(b),
127 (Val(a), Val(b)) => a.cmp(b),
128 (Add(a), Add(b))
129 | (Mul(a), Mul(b))
130 | (Broadcast(a), Broadcast(b))
131 | (Min(a), Min(b))
132 | (Max(a), Max(b)) => a.len().cmp(&b.len()).then(
133 a.iter()
134 .zip(b.iter())
135 .fold(Ordering::Equal, |acc, (a, b)| acc.then_with(|| tdim_lexi_order(a, b))),
136 ),
137 (MulInt(p, d), MulInt(q, e)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),
138 (Div(d, p), Div(e, q)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),
139 (Sym(_), _) => Ordering::Less,
140 (_, Sym(_)) => Ordering::Greater,
141 (Val(_), _) => Ordering::Less,
142 (_, Val(_)) => Ordering::Greater,
143 (Add(_), _) => Ordering::Less,
144 (_, Add(_)) => Ordering::Greater,
145 (Mul(_), _) => Ordering::Less,
146 (_, Mul(_)) => Ordering::Greater,
147 (MulInt(_, _), _) => Ordering::Less,
148 (_, MulInt(_, _)) => Ordering::Greater,
149 (Broadcast(_), _) => Ordering::Less,
150 (_, Broadcast(_)) => Ordering::Greater,
151 (Min(_), _) => Ordering::Less,
152 (_, Min(_)) => Ordering::Greater,
153 (Max(_), _) => Ordering::Less,
154 (_, Max(_)) => Ordering::Greater,
155 (Ge(a1, b1), Ge(a2, b2)) | (Eq(a1, b1), Eq(a2, b2)) => {
156 tdim_lexi_order(a1, a2).then_with(|| tdim_lexi_order(b1, b2))
157 }
158 (Ge(_, _) | Eq(_, _), _) => Ordering::Less,
159 (_, Ge(_, _) | Eq(_, _)) => Ordering::Greater,
160 }
161}
162
163fn try_divide_multiple_plus_remainder(
179 terms: &[TDim],
180 q: u64,
181 scope: &SymbolScopeData,
182 extra: &[Assertion],
183) -> Option<TDim> {
184 let mut quotients: Vec<TDim> = vec![];
185 let mut const_rem: i64 = 0;
186 let mut any_extracted = false;
187 for term in terms {
188 match term {
189 MulInt(c, x) if *c != 0 && c.rem_euclid(q as i64) == 0 => {
190 if !scope.prove_positive_or_zero_with_extra(x, extra) {
191 return None;
192 }
193 let new_coeff = c / (q as i64);
194 quotients.push(if new_coeff == 1 {
195 (**x).clone()
196 } else if new_coeff == -1 {
197 MulInt(-1, x.clone())
198 } else {
199 MulInt(new_coeff, x.clone())
200 });
201 any_extracted = true;
202 }
203 Val(v) => const_rem += v,
204 _ => return None,
205 }
206 }
207 if !any_extracted {
208 return None;
209 }
210 if !(0..q as i64).contains(&const_rem) {
211 return None;
212 }
213 Some(if quotients.len() == 1 { quotients.remove(0) } else { Add(quotients) })
214}
215
216impl fmt::Display for TDim {
217 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
218 match &self {
219 Sym(sym) => write!(fmt, "{sym}"),
220 Val(it) => write!(fmt, "{it}"),
221 Add(it) => write!(fmt, "{}", it.iter().map(|x| format!("{x}")).join("+")),
222 Mul(it) => write!(fmt, "{}", it.iter().map(|x| format!("({x})")).join("*")),
223 Broadcast(it) => {
224 write!(fmt, "broadcast({})", it.iter().map(|x| format!("({x})")).join(", "))
225 }
226 Min(it) => write!(fmt, "min({})", it.iter().map(|x| format!("{x}")).join(",")),
227 Max(it) => write!(fmt, "max({})", it.iter().map(|x| format!("{x}")).join(",")),
228 MulInt(a, b) => write!(fmt, "{a}*{b}"),
229 Div(a, b) => write!(fmt, "({a})/{b}"),
230 Ge(a, b) => write!(fmt, "({a}>={b})"),
231 Eq(a, b) => write!(fmt, "({a}=={b})"),
232 }
233 }
234}
235
236impl TDim {
237 #[inline]
238 pub fn is_one(&self) -> bool {
239 matches!(self, Val(1))
240 }
241
242 #[inline]
243 pub fn to_i64(&self) -> Result<i64, TooEarly> {
249 if let Val(v) = self { Ok(*v) } else { Err(TooEarly::UndeterminedSymbol(self.to_string())) }
250 }
251
252 #[inline]
253 pub fn as_i64(&self) -> Option<i64> {
254 if let Val(v) = self { Some(*v) } else { None }
255 }
256
257 pub fn maybe_eval_to_i64(&self, values: &SymbolValues) -> Option<i64> {
263 match self {
264 Sym(sym) => values.get(sym),
265 Val(v) => Some(*v),
266 Add(terms) => terms
267 .iter()
268 .try_fold(0i64, |acc, it| acc.checked_add(it.maybe_eval_to_i64(values)?)),
269 Mul(terms) => terms
270 .iter()
271 .try_fold(1i64, |acc, it| acc.checked_mul(it.maybe_eval_to_i64(values)?)),
272 Min(terms) => terms
273 .iter()
274 .try_fold(i64::MAX, |acc, it| Some(acc.min(it.maybe_eval_to_i64(values)?))),
275 Max(terms) => terms
276 .iter()
277 .try_fold(i64::MIN, |acc, it| Some(acc.max(it.maybe_eval_to_i64(values)?))),
278 Broadcast(terms) => terms.iter().try_fold(1i64, |acc, it| {
279 (acc as usize)
280 .broadcast(it.maybe_eval_to_i64(values)? as usize)
281 .ok()
282 .map(|x| x as i64)
283 }),
284 Div(a, q) => Some(a.maybe_eval_to_i64(values)? / *q as i64),
285 MulInt(p, a) => a.maybe_eval_to_i64(values)?.checked_mul(*p),
286 Ge(a, b) => Some((a.maybe_eval_to_i64(values)? >= b.maybe_eval_to_i64(values)?) as i64),
287 Eq(a, b) => Some((a.maybe_eval_to_i64(values)? == b.maybe_eval_to_i64(values)?) as i64),
288 }
289 }
290
291 pub fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64> {
292 match self {
293 Sym(sym) => {
294 let Some(v) = values.get(sym) else {
295 Err(TooEarly::UndeterminedSymbol(self.to_string()))?
296 };
297 Ok(v)
298 }
299 Val(v) => Ok(*v),
300 Add(terms) => terms.iter().try_fold(0i64, |acc, it| {
301 let x = it.eval_to_i64(values)?;
302 acc.checked_add(x)
303 .with_context(|| format!("Overflow in TDim addition ({acc} + {x})"))
304 }),
305 Mul(terms) => terms.iter().try_fold(1i64, |acc, it| {
306 let x = it.eval_to_i64(values)?;
307 acc.checked_mul(x)
308 .with_context(|| format!("Overflow in TDim multiplication ({acc} * {x})"))
309 }),
310 Min(terms) => terms
311 .iter()
312 .try_fold(i64::MAX, |acc, it| it.eval_to_i64(values).map(|x| acc.min(x))),
313 Max(terms) => terms
314 .iter()
315 .try_fold(i64::MIN, |acc, it| it.eval_to_i64(values).map(|x| acc.max(x))),
316 Broadcast(terms) => terms.iter().try_fold(1i64, |acc, it| {
317 it.eval_to_i64(values)
318 .and_then(|x| ((acc as usize).broadcast(x as usize)).map(|x| x as i64))
319 }),
320 Div(a, q) => Ok(a.eval_to_i64(values)? / *q as i64),
321 MulInt(p, a) => {
322 let x = a.eval_to_i64(values)?;
323 x.checked_mul(*p)
324 .with_context(|| format!("Overflow in TDim multiplication ({x} * {p})"))
325 }
326 Ge(a, b) => Ok(if a.eval_to_i64(values)? >= b.eval_to_i64(values)? { 1 } else { 0 }),
327 Eq(a, b) => Ok(if a.eval_to_i64(values)? == b.eval_to_i64(values)? { 1 } else { 0 }),
328 }
329 }
330
331 pub fn eval(&self, values: &SymbolValues) -> TDim {
332 match self {
333 Sym(sym) => values.get(sym).map(Val).unwrap_or_else(|| Sym(sym.clone())),
334 Val(v) => Val(*v),
335 Add(terms) => terms.iter().fold(Val(0), |acc, it| -> TDim { acc + it.eval(values) }),
336 Mul(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim { acc * it.eval(values) }),
337 Min(terms) => {
338 terms.iter().fold(Val(i64::MAX), |acc, it| -> TDim { acc.mini(it.eval(values)) })
339 }
340 Max(terms) => {
341 terms.iter().fold(Val(i64::MIN), |acc, it| -> TDim { acc.maxi(it.eval(values)) })
342 }
343 Broadcast(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim {
344 acc.broadcast(it.eval(values)).unwrap_or_else(|_| self.clone())
345 }),
346 Div(a, q) => a.eval(values) / *q as i64,
347 MulInt(p, a) => a.eval(values) * *p,
348 Ge(a, b) => {
349 let a2 = a.eval(values);
350 let b2 = b.eval(values);
351 if let (Val(av), Val(bv)) = (&a2, &b2) {
352 Val(if av >= bv { 1 } else { 0 })
353 } else {
354 Ge(b!(a2), b!(b2))
355 }
356 }
357 Eq(a, b) => {
358 let a2 = a.eval(values);
359 let b2 = b.eval(values);
360 if let (Val(av), Val(bv)) = (&a2, &b2) {
361 Val(if av == bv { 1 } else { 0 })
362 } else {
363 Eq(b!(a2), b!(b2))
364 }
365 }
366 }
367 }
368
369 pub fn eval_with_scenario(&self, scenario: &str) -> TDim {
370 if let Val(v) = self {
371 return Val(*v);
372 }
373 let scope = self.find_scope().unwrap();
374 let scope = scope.0;
375 let locked = scope.lock();
376 let scope = locked.borrow();
377 self.clone().simplify_rec(&scope, Some(scenario), &[])
378 }
379
380 pub fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self> {
381 self.substitute_all(&std::collections::HashMap::from([(from.clone(), to.clone())]))
382 }
383
384 pub fn substitute_all(
385 &self,
386 map: &std::collections::HashMap<Symbol, Self>,
387 ) -> TractResult<Self> {
388 match self {
389 Sym(sym) => Ok(map.get(sym).cloned().unwrap_or_else(|| self.clone())),
390 Val(v) => Ok(Val(*v)),
391 Add(terms) => terms.iter().try_fold(Val(0), |acc, it| -> TractResult<TDim> {
392 Ok(acc + it.substitute_all(map)?)
393 }),
394 Mul(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {
395 Ok(acc * it.substitute_all(map)?)
396 }),
397 Broadcast(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {
398 acc.broadcast(it.substitute_all(map)?)
399 }),
400 Min(terms) => terms.iter().try_fold(Val(i64::MAX), |acc, it| -> TractResult<TDim> {
401 Ok(acc.mini(it.substitute_all(map)?))
402 }),
403 Max(terms) => terms.iter().try_fold(Val(i64::MIN), |acc, it| -> TractResult<TDim> {
404 Ok(acc.maxi(it.substitute_all(map)?))
405 }),
406 Div(a, q) => Ok(a.substitute_all(map)? / *q as i64),
407 MulInt(p, a) => Ok(a.substitute_all(map)? * *p),
408 Ge(a, b) => Ok(Ge(b!(a.substitute_all(map)?), b!(b.substitute_all(map)?))),
409 Eq(a, b) => Ok(Eq(b!(a.substitute_all(map)?), b!(b.substitute_all(map)?))),
410 }
411 }
412
413 pub fn reduce(self) -> TDim {
414 self.simplify()
415 .wiggle()
416 .into_iter()
417 .sorted_by(tdim_lexi_order)
418 .unique()
419 .map(|e| e.simplify())
420 .min_by_key(|e| e.cost())
421 .unwrap()
422 }
423
424 fn cost(&self) -> usize {
425 use self::TDim::*;
426 match self {
427 Sym(_) | Val(_) => 1,
428 Add(terms) => 2 * terms.iter().map(TDim::cost).sum::<usize>(),
429 Mul(terms) => 3 * terms.iter().map(TDim::cost).sum::<usize>(),
430 Broadcast(terms) => 4 * terms.iter().map(TDim::cost).sum::<usize>(),
431 Min(terms) | Max(terms) => 5 * terms.iter().map(TDim::cost).sum::<usize>(),
432 Div(a, _) => 3 * a.cost(),
433 MulInt(_, a) => 2 * a.cost(),
434 Ge(a, b) | Eq(a, b) => 5 * (a.cost() + b.cost()),
435 }
436 }
437
438 fn wiggle(&self) -> Vec<TDim> {
439 use self::TDim::*;
440 match self {
441 Sym(_) | Val(_) | Mul(_) | Broadcast(_) | Min(_) | Max(_) | Ge(_, _) | Eq(_, _) => {
442 vec![self.clone()]
443 }
444 Add(terms) => {
445 let mut forms = vec![];
446 let sub_exprs = terms.iter().map(|e| e.wiggle()).multi_cartesian_product();
447
448 fn first_div_term(terms: &[TDim]) -> Option<(usize, &TDim, u64)> {
449 terms.iter().enumerate().find_map(|(index, t)| match t {
450 Div(numerator, quotient) => Some((index, &**numerator, *quotient)),
451 _ => None,
452 })
453 }
454
455 fn generate_new_numerator(
456 div_index: usize,
457 numerator: &TDim,
458 quotient: u64,
459 expr: &[TDim],
460 ) -> Vec<TDim> {
461 expr.iter()
462 .enumerate()
463 .map(|(index, term)| {
464 if index == div_index {
465 numerator.clone()
466 } else {
467 MulInt(quotient as i64, Box::new(term.clone()))
468 }
469 })
470 .collect()
471 }
472
473 for expr in sub_exprs {
474 if let Some((div_index, numerator, quotient)) = first_div_term(&expr) {
475 let new_numerator =
476 generate_new_numerator(div_index, numerator, quotient, &expr);
477 forms.push(Div(Box::new(Add(new_numerator)), quotient))
478 }
479
480 forms.push(Add(expr));
481 }
482 forms
483 }
484 MulInt(p, a) => a.wiggle().into_iter().map(|a| MulInt(*p, b!(a))).collect(),
485 Div(a, q) => {
486 let mut forms = vec![];
487 for num in a.wiggle() {
488 if let Add(terms) = &num {
489 let (integer, non_integer): (Vec<_>, Vec<_>) =
490 terms.iter().cloned().partition(|a| a.gcd() % q == 0);
491 if !non_integer.iter().any(|t| matches!(t, Val(_))) {
502 let mut new_terms =
503 integer.iter().map(|i| i.div(*q)).collect::<Vec<_>>();
504 if non_integer.len() > 0 {
505 new_terms.push(Div(b!(Add(non_integer)), *q));
506 }
507 forms.push(Add(new_terms))
508 }
509 }
510 forms.push(Div(b!(num), *q))
511 }
512 forms
513 }
514 }
515 }
516
517 fn find_any_sym(tdim: &TDim) -> Option<&Symbol> {
518 match tdim {
519 Val(_) => None,
520 Sym(s) => Some(s),
521 Add(terms) | Mul(terms) | Min(terms) | Max(terms) | Broadcast(terms) => {
522 terms.iter().find_map(Self::find_any_sym)
523 }
524 MulInt(_, t) | Div(t, _) => Self::find_any_sym(t),
525 Ge(a, b) | Eq(a, b) => Self::find_any_sym(a).or_else(|| Self::find_any_sym(b)),
526 }
527 }
528
529 pub fn find_scope(&self) -> Option<SymbolScope> {
530 Self::find_any_sym(self).and_then(|s| s.scope().clone())
531 }
532
533 pub fn expand_polynomial(self) -> TDim {
542 use self::TDim::*;
543 match self {
544 Mul(terms) => {
545 let terms: Vec<TDim> = terms.into_iter().map(Self::expand_polynomial).collect();
546 if let Some(add_idx) = terms.iter().position(|t| matches!(t, Add(_))) {
547 let Add(add_terms) = terms[add_idx].clone() else { unreachable!() };
548 let others: Vec<TDim> = terms
549 .iter()
550 .enumerate()
551 .filter(|(i, _)| *i != add_idx)
552 .map(|(_, t)| t.clone())
553 .collect();
554 Add(add_terms
555 .into_iter()
556 .map(|t| {
557 let mut product = others.clone();
558 product.push(t);
559 Mul(product).expand_polynomial()
560 })
561 .collect())
562 .simplify()
563 } else {
564 Mul(terms).simplify()
565 }
566 }
567 MulInt(c, inner) => MulInt(c, Box::new(inner.expand_polynomial())).simplify(),
568 Add(terms) => Add(terms.into_iter().map(Self::expand_polynomial).collect()).simplify(),
569 Div(a, q) => Div(Box::new(a.expand_polynomial()), q).simplify(),
570 Min(terms) => Min(terms.into_iter().map(Self::expand_polynomial).collect()).simplify(),
571 Max(terms) => Max(terms.into_iter().map(Self::expand_polynomial).collect()).simplify(),
572 Broadcast(terms) => {
573 Broadcast(terms.into_iter().map(Self::expand_polynomial).collect()).simplify()
574 }
575 Ge(a, b) => {
576 Ge(Box::new(a.expand_polynomial()), Box::new(b.expand_polynomial())).simplify()
577 }
578 Eq(a, b) => {
579 Eq(Box::new(a.expand_polynomial()), Box::new(b.expand_polynomial())).simplify()
580 }
581 it @ (Sym(_) | Val(_)) => it,
582 }
583 }
584
585 pub fn simplify(self) -> TDim {
586 use self::TDim::*;
587 if let Some(v) = self.maybe_eval_to_i64(&SymbolValues::default()) {
588 return Val(v);
589 }
590 let Some(scope) = self.find_scope() else {
591 return self;
592 };
593 let scope = scope.0;
594 let locked = scope.lock();
595 let scope = locked.borrow();
596 let it = self.simplify_rec(&scope, None, &[]);
597 let mut current: Option<TDim> = None;
598 for scenario in scope.scenarios() {
599 let v = it.clone().simplify_rec(&scope, Some(scenario), &[]);
600 if current.is_some_and(|c| c != v) {
601 return it;
602 } else {
603 current = Some(v);
604 }
605 }
606 current.unwrap_or(it)
607 }
608
609 pub fn simplify_with_extra_assertions(self, extra: &[Assertion]) -> TDim {
610 use self::TDim::*;
611 if extra.is_empty() {
612 return self.simplify();
613 }
614 if let Some(v) = self.maybe_eval_to_i64(&SymbolValues::default()) {
615 return Val(v);
616 }
617 let Some(scope) = self.find_scope() else {
618 return self;
619 };
620 let scope = scope.0;
621 let locked = scope.lock();
622 let scope = locked.borrow();
623 let it = self.simplify_rec(&scope, None, extra);
624 let mut current: Option<TDim> = None;
625 for scenario in scope.scenarios() {
626 let v = it.clone().simplify_rec(&scope, Some(scenario), extra);
627 if current.is_some_and(|c| c != v) {
628 return it;
629 } else {
630 current = Some(v);
631 }
632 }
633 current.unwrap_or(it)
634 }
635
636 fn simplify_rec(
637 self,
638 scope: &SymbolScopeData,
639 scenario: Option<&str>,
640 extra: &[Assertion],
641 ) -> TDim {
642 match self {
643 Add(mut terms) => {
644 #[allow(clippy::mutable_key_type)]
645 let mut simplified_terms: HashMap<TDim, i64> = HashMap::new();
646 while let Some(term) = terms.pop() {
648 let simplified = term.simplify_rec(scope, scenario, extra);
649 match simplified {
650 Val(0) => {} Add(members) => {
652 terms.extend(members);
653 continue;
654 }
655 Val(value) => *simplified_terms.entry(Val(1)).or_insert(0) += value,
656 MulInt(value, factor) => {
657 *simplified_terms.entry((*factor).clone()).or_insert(0) += value;
658 }
659 n => *simplified_terms.entry(n).or_insert(0) += 1,
660 };
661 }
662
663 pub fn evaluate_count(term: TDim, count: i64) -> Option<TDim> {
664 match count {
665 0 => None,
666 _ if term == TDim::Val(1) => Some(TDim::Val(count)),
667 1 => Some(term),
668 _ => Some(TDim::MulInt(count, Box::new(term))),
669 }
670 }
671
672 let has_non_const =
687 simplified_terms.iter().any(|(k, &c)| c != 0 && !matches!(k, Val(_)));
688 let coef_gcd = if has_non_const {
689 simplified_terms
690 .values()
691 .filter(|&&c| c != 0)
692 .map(|c| c.unsigned_abs() as i64)
693 .reduce(|a, b| a.gcd(&b))
694 .unwrap_or(0)
695 } else {
696 0
697 };
698 let outer_factor = if coef_gcd > 1 {
699 for v in simplified_terms.values_mut() {
700 *v /= coef_gcd;
701 }
702 Some(coef_gcd)
703 } else {
704 None
705 };
706
707 let mut members: Vec<TDim> = simplified_terms
708 .into_iter()
709 .filter_map(|(term, count)| evaluate_count(term, count))
710 .collect();
711 members.sort_by(tdim_lexi_order);
712
713 let inner = match members.len() {
714 0 => TDim::Val(0),
715 1 => members.into_iter().next().unwrap(),
716 _ => TDim::Add(members),
717 };
718 match outer_factor {
719 None => inner,
720 Some(_) if matches!(inner, TDim::Val(0)) => TDim::Val(0),
721 Some(g) => TDim::MulInt(g, Box::new(inner)),
722 }
723 }
724 Mul(terms) => {
725 {
738 let add_indices: Vec<usize> = terms
739 .iter()
740 .enumerate()
741 .filter(|(_, t)| matches!(t, Add(_)))
742 .map(|(i, _)| i)
743 .collect();
744 if add_indices.len() == 1 {
745 let add_idx = add_indices[0];
746 let Add(add_terms) = &terms[add_idx] else { unreachable!() };
747 let other_factors: Vec<TDim> = terms
748 .iter()
749 .enumerate()
750 .filter(|(i, _)| *i != add_idx)
751 .map(|(_, t)| t.clone())
752 .collect();
753 let distributed: Vec<TDim> = add_terms
754 .iter()
755 .map(|at| {
756 let mut product = other_factors.clone();
757 product.push(at.clone());
758 Mul(product)
759 })
760 .collect();
761 return Add(distributed).simplify_rec(scope, scenario, extra);
762 }
763 }
764
765 let mut flattened_terms = vec![];
768 for t in terms {
769 match t.clone().reduce() {
770 Mul(inner_terms) => flattened_terms.extend(inner_terms),
771 MulInt(k, inner) => {
772 flattened_terms.push(Val(k));
773 flattened_terms.push(*inner);
774 }
775 other => flattened_terms.push(other),
776 }
777 }
778 let mut terms = flattened_terms;
779
780 let mut gcd = Mul(terms.clone()).gcd() as i64;
781 if gcd == 0 {
782 return Val(0);
783 }
784 terms = if gcd != 1 {
785 terms
786 .into_iter()
787 .map(|t| {
788 let gcd = t.gcd();
789 (t / gcd).simplify_rec(scope, scenario, extra)
790 })
791 .collect()
792 } else {
793 terms
794 };
795 if terms.iter().filter(|t| t == &&Val(-1)).count() % 2 == 1 {
796 gcd = -gcd;
797 }
798 terms.retain(|t| !t.is_one() && t != &Val(-1));
799 terms.sort_by(tdim_lexi_order);
800
801 match (gcd, terms.len()) {
802 (_, 0) => Val(gcd), (0, _) => Val(0), (1, 1) => terms.remove(0), (1, _) => Mul(terms), (_, 1) => MulInt(gcd, Box::new(terms.remove(0))), _ => MulInt(gcd, Box::new(Mul(terms))), }
810 }
811 MulInt(coef, expr) => {
812 match *expr {
813 MulInt(c2, inner) => {
814 if let Some(c) = coef.checked_mul(c2) {
815 return MulInt(c, inner).simplify_rec(scope, scenario, extra);
816 } else {
817 return MulInt(coef, Box::new(MulInt(c2, inner)));
818 }
819 }
820 Val(v) => {
821 return coef
822 .checked_mul(v)
823 .map(Val)
824 .unwrap_or_else(|| MulInt(coef, Box::new(Val(v))));
825 }
826 _ => {}
827 }
828
829 let simplified = expr.simplify_rec(scope, scenario, extra);
830 match (coef, simplified) {
831 (0, _) => Val(0), (1, s) => s, (_, Add(terms)) => Add(terms
834 .into_iter()
835 .map(|term| {
836 MulInt(coef, Box::new(term)).simplify_rec(scope, scenario, extra)
837 })
838 .collect()), (c, Val(v)) => {
840 c.checked_mul(v).map(Val).unwrap_or_else(|| MulInt(c, Box::new(Val(v))))
841 } (c, MulInt(v, inner)) => {
843 if let Some(cv) = c.checked_mul(v) {
844 MulInt(cv, inner) } else {
846 MulInt(c, Box::new(MulInt(v, inner)))
847 }
848 }
849 (_, s) => MulInt(coef, Box::new(s)), }
851 }
852 Div(a, q) => {
853 if q == 1 {
854 return a.simplify_rec(scope, scenario, extra);
855 } else if let Div(a, q2) = *a {
856 return Div(a, q * q2).simplify_rec(scope, scenario, extra);
857 }
858 let a = a.simplify_rec(scope, scenario, extra);
859 if let Val(a) = a {
860 Val(a / q as i64)
861 } else if let MulInt(-1, a) = a {
862 MulInt(-1, b!(Div(a, q)))
863 } else if let Add(mut terms) = a {
864 if terms
865 .iter()
866 .any(|t| if let MulInt(-1, s) = t { matches!(&**s, Sym(_)) } else { false })
867 {
868 MulInt(
869 -1,
870 b!(Div(
871 b!(Add(terms.into_iter().map(|t| MulInt(-1, b!(t))).collect())
872 .simplify_rec(scope, scenario, extra)),
873 q
874 )),
875 )
876 } else if let Some(val) = terms
877 .iter()
878 .find_map(|t| if let Val(v) = t { Some(*v) } else { None })
879 .and_then(|v| {
880 if v >= q as i64 {
881 Some(v / q as i64)
882 } else if v < 0 {
883 Some(-Integer::div_ceil(&-v, &(q as i64)))
884 } else {
885 None
886 }
887 })
888 {
889 terms.push(Val(-val * q as i64));
890 let inner = Div(b!(Add(terms).simplify_rec(scope, scenario, extra)), q)
894 .simplify_rec(scope, scenario, extra);
895 Add(vec![Val(val), inner])
896 } else if let Some(simplified) =
897 try_divide_multiple_plus_remainder(&terms, q, scope, extra)
898 {
899 simplified.simplify_rec(scope, scenario, extra)
908 } else if let Some(found_idx) = terms.iter().position(|term| {
909 matches!(term, MulInt(p, inner)
913 if *p == -(q as i64)
914 && matches!(inner.as_ref(), Div(_, q2) if *q2 == q))
915 }) {
916 let MulInt(_, inner) = &terms[found_idx] else { unreachable!() };
917 let Div(y, _) = inner.as_ref() else { unreachable!() };
918 let remaining: Vec<TDim> = terms
919 .iter()
920 .enumerate()
921 .filter(|&(i, _)| i != found_idx)
922 .map(|(_, t)| t.clone())
923 .collect();
924 let remaining_sum = match remaining.len() {
925 0 => Val(0),
926 1 => remaining.into_iter().next().unwrap(),
927 _ => Add(remaining),
928 };
929 if eq_structural(&remaining_sum, y) {
930 Val(0)
931 } else {
932 Div(b!(Add(terms)), q)
933 }
934 } else {
935 Div(b!(Add(terms)), q)
936 }
937 } else if let MulInt(p, a) = a {
938 if p == q as i64 {
939 a.simplify()
940 } else {
941 let gcd = p.abs().gcd(&(q as i64));
942 if gcd == p {
943 Div(a, q / gcd as u64)
944 } else if gcd == q as i64 {
945 MulInt(p / gcd, a)
946 } else if gcd > 1 {
947 Div(b!(MulInt(p / gcd, a)), q / gcd as u64)
948 .simplify_rec(scope, scenario, extra)
949 } else {
950 Div(b!(MulInt(p, a)), q)
951 }
952 }
953 } else {
954 Div(b!(a), q)
955 }
956 }
957 Broadcast(terms) => {
958 let mut terms: Vec<TDim> = terms
959 .iter()
960 .map(|s| s.clone().simplify_rec(scope, scenario, extra))
961 .flat_map(|t| if let Broadcast(t) = t { t } else { vec![t] })
962 .filter(|t| !t.is_one())
963 .sorted_by(tdim_lexi_order)
964 .dedup()
965 .collect_vec();
966 match &*terms {
968 [] => Val(1),
969 [_] => terms.remove(0),
970 [a, Min(m)] | [Min(m), a]
971 if m.contains(a)
972 && m.iter()
973 .all(|t| scope.prove_strict_positive_with_extra(t, extra)) =>
974 {
975 a.clone()
976 }
977 _ => Broadcast(terms),
978 }
979 }
980
981 Min(terms) => {
982 let mut flatten: Vec<TDim> = terms
983 .into_iter()
984 .map(|t| t.simplify_rec(scope, scenario, extra))
985 .flat_map(|t| if let Min(t) = t { t } else { vec![t] })
986 .filter(|t| t != &Val(i64::MAX))
987 .sorted_by(tdim_lexi_order)
988 .dedup()
989 .collect();
990 #[allow(clippy::mutable_key_type)]
991 let mut redundant = HashSet::<TDim>::default();
992 for pair in flatten.iter().permutations(2) {
993 let (a, b) = (pair[0], pair[1]);
994 if redundant.contains(a) || redundant.contains(b) {
995 continue;
996 }
997 let diff = a.clone() - b;
998 if diff.as_i64().is_some_and(|i| i >= 0)
999 || scope.prove_positive_or_zero_with_extra(&diff, extra)
1000 {
1001 redundant.insert(a.clone());
1002 }
1003 }
1004 flatten.retain(|t| !redundant.contains(t));
1005 if flatten.len() == 0 {
1006 i64::MAX.to_dim()
1007 } else if flatten.len() == 1 {
1008 flatten.into_iter().next().unwrap()
1009 } else {
1010 Min(flatten)
1011 }
1012 }
1013 Max(terms) => {
1014 let mut flatten: Vec<TDim> = terms
1015 .into_iter()
1016 .map(|t| t.simplify_rec(scope, scenario, extra))
1017 .flat_map(|t| if let Max(t) = t { t } else { vec![t] })
1018 .filter(|t| t != &Val(i64::MIN))
1019 .sorted_by(tdim_lexi_order)
1020 .dedup()
1021 .collect();
1022 #[allow(clippy::mutable_key_type)]
1023 let mut redundant = HashSet::<TDim>::default();
1024 for pair in flatten.iter().permutations(2) {
1025 let (a, b) = (pair[0], pair[1]);
1026 if redundant.contains(a) || redundant.contains(b) {
1027 continue;
1028 }
1029 let diff = a.clone() - b;
1030 if diff.as_i64().is_some_and(|i| i >= 0)
1031 || scope.prove_positive_or_zero_with_extra(&diff, extra)
1032 {
1033 redundant.insert(b.clone());
1034 }
1035 }
1036 flatten.retain(|t| !redundant.contains(t));
1037 if flatten.len() == 0 {
1038 i64::MIN.to_dim()
1039 } else if flatten.len() == 1 {
1040 flatten.into_iter().next().unwrap()
1041 } else {
1042 Max(flatten)
1043 }
1044 }
1045 Sym(s) => scope
1046 .assertions(scenario)
1047 .find_map(|a| match a {
1048 Assertion::Eq(Sym(sym), v) if sym == &s => Some(v.clone()),
1049 _ => None,
1050 })
1051 .unwrap_or(Sym(s)),
1052 Val(_) => self,
1053 Ge(a, b) => {
1054 let a = a.simplify_rec(scope, scenario, extra);
1055 let b = b.simplify_rec(scope, scenario, extra);
1056 match (&a, &b) {
1057 (Val(av), Val(bv)) => Val(if av >= bv { 1 } else { 0 }),
1058 _ => {
1059 let diff = a.clone() - b.clone();
1060 if scope.prove_positive_or_zero_with_extra(&diff, extra) {
1061 Val(1)
1062 } else if scope
1063 .prove_strict_positive_with_extra(&(b.clone() - a.clone()), extra)
1064 {
1065 Val(0)
1066 } else {
1067 Ge(b!(a), b!(b))
1068 }
1069 }
1070 }
1071 }
1072 Eq(a, b) => {
1073 let a = a.simplify_rec(scope, scenario, extra);
1074 let b = b.simplify_rec(scope, scenario, extra);
1075 match (&a, &b) {
1076 (Val(av), Val(bv)) => Val(if av == bv { 1 } else { 0 }),
1077 _ => {
1078 let diff = a.clone() - b.clone();
1079 if scope.prove_strict_positive_with_extra(&diff, extra)
1080 || scope
1081 .prove_strict_positive_with_extra(&(b.clone() - a.clone()), extra)
1082 {
1083 Val(0)
1084 } else {
1085 let boolean_case = match (&a, &b) {
1090 (Val(0), e) | (e, Val(0)) => Some((e, false)),
1091 (Val(1), e) | (e, Val(1)) => Some((e, true)),
1092 _ => None,
1093 };
1094 if let Some((expr, equals_one)) = boolean_case
1095 && scope.prove_positive_or_zero_with_extra(expr, extra)
1096 && scope.prove_positive_or_zero_with_extra(
1097 &(Val(1) - expr.clone()),
1098 extra,
1099 )
1100 {
1101 return if equals_one {
1102 expr.clone()
1103 } else {
1104 (Val(1) - expr.clone()).simplify_rec(scope, scenario, extra)
1105 };
1106 }
1107 Eq(b!(a), b!(b))
1108 }
1109 }
1110 }
1111 }
1112 }
1113 }
1114
1115 pub(super) fn inclusive_bound(&self, scope: &SymbolScopeData, upper: bool) -> Option<i64> {
1116 use self::TDim::*;
1117 match self {
1118 Val(n) => Some(*n),
1119 Sym(_) => {
1120 if upper {
1121 scope
1122 .all_assertions()
1123 .iter()
1124 .filter_map(|assert| match &assert {
1125 Assertion::LT(left, right)
1126 if left == self && right.as_i64().is_some() =>
1127 {
1128 Some(right.as_i64().unwrap() - 1)
1129 }
1130 Assertion::LTE(left, right)
1131 if left == self && right.as_i64().is_some() =>
1132 {
1133 Some(right.as_i64().unwrap())
1134 }
1135 _ => None,
1136 })
1137 .min()
1138 } else {
1139 scope
1140 .all_assertions()
1141 .iter()
1142 .filter_map(|assert| match &assert {
1143 Assertion::GT(left, right)
1144 if left == self && right.as_i64().is_some() =>
1145 {
1146 Some(right.as_i64().unwrap() + 1)
1147 }
1148 Assertion::GTE(left, right)
1149 if left == self && right.as_i64().is_some() =>
1150 {
1151 Some(right.as_i64().unwrap())
1152 }
1153 _ => None,
1154 })
1155 .max()
1156 }
1157 }
1158 Add(terms) => {
1159 let mut bound: i64 = 0;
1160 for t in terms {
1161 {
1162 let b = t.inclusive_bound(scope, upper)?;
1163 bound = bound.checked_add(b)?;
1164 }
1165 }
1166 Some(bound)
1167 }
1168 MulInt(p, a) => match p.cmp(&0) {
1169 Ordering::Equal => Some(0),
1170 Ordering::Greater => {
1171 a.inclusive_bound(scope, upper).and_then(|x| x.checked_mul(*p))
1172 }
1173 Ordering::Less => a.inclusive_bound(scope, !upper).and_then(|x| x.checked_mul(*p)),
1174 },
1175 Mul(terms) => {
1176 let mut lo: i64 = 1;
1178 let mut hi: i64 = 1;
1179 for t in terms {
1180 let t_lo = t.inclusive_bound(scope, false)?;
1181 let t_hi = t.inclusive_bound(scope, true)?;
1182 if t_lo < 0 {
1183 return None;
1184 }
1185 lo = lo.checked_mul(t_lo)?;
1186 hi = hi.checked_mul(t_hi)?;
1187 }
1188 Some(if upper { hi } else { lo })
1189 }
1190 Min(terms) if !upper => {
1191 let bounds: Option<Vec<i64>> =
1194 terms.iter().map(|t| t.inclusive_bound(scope, false)).collect();
1195 bounds.map(|b| b.into_iter().min().unwrap_or(i64::MAX))
1196 }
1197 Max(terms) if upper => {
1198 let bounds: Option<Vec<i64>> =
1201 terms.iter().map(|t| t.inclusive_bound(scope, true)).collect();
1202 bounds.map(|b| b.into_iter().max().unwrap_or(i64::MIN))
1203 }
1204 Div(a, q) => a.inclusive_bound(scope, upper).map(|x| x / (*q as i64)),
1205 Broadcast(terms) => {
1206 if upper {
1207 Max(terms.clone()).inclusive_bound(scope, true)
1208 } else {
1209 Min(terms.clone()).inclusive_bound(scope, false)
1210 }
1211 }
1212 Ge(_, _) | Eq(_, _) => {
1213 if upper {
1214 Some(1)
1215 } else {
1216 Some(0)
1217 }
1218 }
1219 _ => None,
1220 }
1221 }
1222
1223 pub fn low_inclusive_bound(&self) -> Option<i64> {
1224 if let TDim::Val(v) = self {
1225 return Some(*v);
1226 }
1227 let scope = self.find_scope()?;
1228 let data = scope.0.lock();
1229 let data = data.borrow();
1230 self.inclusive_bound(&data, false)
1231 }
1232
1233 pub fn high_inclusive_bound(&self) -> Option<i64> {
1234 if let TDim::Val(v) = self {
1235 return Some(*v);
1236 }
1237 let scope = self.find_scope()?;
1238 let data = scope.0.lock();
1239 let data = data.borrow();
1240 self.inclusive_bound(&data, true)
1241 }
1242
1243 pub fn prove_positive_or_zero(&self) -> bool {
1244 if let TDim::Val(v) = self {
1245 return *v >= 0;
1246 }
1247 let Some(scope) = self.find_scope() else { return false };
1248 let data = scope.0.lock();
1249 let data = data.borrow();
1250 data.prove_positive_or_zero(self)
1251 }
1252
1253 pub fn prove_strict_positive(&self) -> bool {
1254 if let TDim::Val(v) = self {
1255 return *v > 0;
1256 }
1257 (self.clone() - 1).prove_positive_or_zero()
1258 }
1259
1260 pub fn prove_negative_or_zero(&self) -> bool {
1261 if let TDim::Val(v) = self {
1262 return *v <= 0;
1263 }
1264 self.clone().neg().prove_positive_or_zero()
1265 }
1266
1267 pub fn prove_strict_negative(&self) -> bool {
1268 if let TDim::Val(v) = self {
1269 return *v < 0;
1270 }
1271 self.clone().neg().prove_strict_positive()
1272 }
1273
1274 pub fn lcm(&self, other: &TDim) -> Option<TDim> {
1282 match (self.as_i64(), other.as_i64()) {
1283 (Some(a), Some(b)) if a > 0 && b > 0 => {
1284 let g = (a as u64).gcd(&(b as u64));
1285 let l = (a as u64 / g).saturating_mul(b as u64);
1286 if l > i64::MAX as u64 { None } else { Some(TDim::Val(l as i64)) }
1287 }
1288 (Some(0), _) | (_, Some(0)) => Some(TDim::Val(0)),
1289 _ => None,
1290 }
1291 }
1292
1293 pub fn gcd(&self) -> u64 {
1294 use self::TDim::*;
1295 match self {
1296 Val(v) => v.unsigned_abs(),
1297 Sym(_) => 1,
1298 Add(terms) => {
1299 let (head, tail) = terms.split_first().unwrap();
1300 tail.iter().fold(head.gcd(), |a, b| a.gcd(&b.gcd()))
1301 }
1302 MulInt(p, a) => a.gcd().saturating_mul(p.unsigned_abs()),
1303 Mul(terms) => terms.iter().map(|t| t.gcd()).fold(1u64, |a, b| a.saturating_mul(b)),
1304 Min(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),
1305 Max(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),
1306 Div(a, q) => {
1307 if a.gcd() % *q == 0 {
1308 a.gcd() / *q
1309 } else {
1310 1
1311 }
1312 }
1313 Broadcast(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap_or(1),
1314 Ge(_, _) | Eq(_, _) => 1,
1315 }
1316 }
1317
1318 fn div(&self, d: u64) -> TDim {
1319 use self::TDim::*;
1320 if d == 1 {
1321 return self.clone();
1322 }
1323 match self {
1324 Val(v) => Val(v / d as i64),
1325 Sym(_) => panic!(),
1326 Add(terms) => Add(terms.iter().map(|t| t.div(d)).collect()),
1327 Min(terms) => Min(terms.iter().map(|t| t.div(d)).collect()),
1328 Max(terms) => Max(terms.iter().map(|t| t.div(d)).collect()),
1329 Broadcast(terms) => Broadcast(terms.iter().map(|t| t.div(d)).collect()),
1330 Mul(_) => Div(Box::new(self.clone()), d),
1331 MulInt(p, a) => {
1332 if *p == d as i64 {
1333 (**a).clone()
1334 } else {
1335 let gcd = p.unsigned_abs().gcd(&d);
1336 MulInt(p / gcd as i64, b!(a.div(d / gcd)))
1337 }
1338 }
1339 Div(a, q) => Div(a.clone(), q * d),
1340 Ge(_, _) | Eq(_, _) => Div(Box::new(self.clone()), d),
1341 }
1342 }
1343
1344 pub fn div_ceil(self, rhs: u64) -> TDim {
1345 TDim::Div(Box::new(Add(vec![self, Val(rhs as i64 - 1)])), rhs).reduce()
1346 }
1347
1348 pub fn guess_slope(&self, sym: &Symbol) -> (i64, u64) {
1349 fn slope_rec(d: &TDim, sym: &Symbol) -> (i64, i64) {
1350 match d {
1351 Val(_) => (0, 1),
1352 Sym(s) => ((sym == s) as i64, 1),
1353 Add(terms) => terms
1354 .iter()
1355 .map(|d| slope_rec(d, sym))
1356 .fold((0, 1), |a, b| ((a.0 * b.1 + a.1 * b.0), (b.1 * a.1))),
1357 Mul(terms) => terms
1358 .iter()
1359 .map(|d| slope_rec(d, sym))
1360 .fold((1, 1), |a, b| ((a.0 * b.0), (b.1 * a.1))),
1361 MulInt(p, a) => {
1362 let (n, d) = slope_rec(a, sym);
1363 (p * n, d)
1364 }
1365 Div(a, q) => {
1366 let (n, d) = slope_rec(a, sym);
1367 (n, d * *q as i64)
1368 }
1369 Broadcast(terms) => slope_rec(&terms[0], sym),
1370 Min(terms) => slope_rec(&terms[0], sym),
1371 Max(terms) => slope_rec(&terms[0], sym),
1372 Ge(_, _) | Eq(_, _) => (0, 1),
1373 }
1374 }
1375 let (p, q) = slope_rec(self, sym);
1376 reduce_ratio(p, q)
1377 }
1378
1379 #[allow(clippy::mutable_key_type)]
1380 pub fn symbols(&self) -> std::collections::HashSet<Symbol> {
1381 match self {
1382 Val(_) => maplit::hashset!(),
1383 Sym(s) => maplit::hashset!(s.clone()),
1384 Add(terms) | Mul(terms) | Broadcast(terms) | Min(terms) | Max(terms) => {
1385 terms.iter().fold(maplit::hashset!(), |mut set, v| {
1386 set.extend(v.symbols());
1387 set
1388 })
1389 }
1390 MulInt(_, a) => a.symbols(),
1391 Div(a, _) => a.symbols(),
1392 Ge(a, b) | Eq(a, b) => {
1393 let mut set = a.symbols();
1394 set.extend(b.symbols());
1395 set
1396 }
1397 }
1398 }
1399
1400 pub fn compatible_with(&self, other: &TDim) -> bool {
1401 if let Some(x) = (self.clone() - other).as_i64() {
1402 return x == 0;
1403 }
1404 true }
1406}
1407
1408pub(super) fn reduce_ratio(mut p: i64, mut q: i64) -> (i64, u64) {
1409 let gcd = p.abs().gcd(&q.abs());
1410 if gcd > 1 {
1411 p /= gcd;
1412 q /= gcd;
1413 }
1414 if q < 0 { (-p, (-q) as u64) } else { (p, q as u64) }
1415}
1416
1417impl Zero for TDim {
1418 fn zero() -> Self {
1419 Val(0)
1420 }
1421 fn is_zero(&self) -> bool {
1422 matches!(self, Val(0))
1423 }
1424}
1425
1426impl Default for TDim {
1427 fn default() -> TDim {
1428 Val(0)
1429 }
1430}
1431
1432impl num_traits::Bounded for TDim {
1433 fn min_value() -> Self {
1434 TDim::Val(i64::MIN)
1435 }
1436
1437 fn max_value() -> Self {
1438 TDim::Val(i64::MAX)
1439 }
1440}
1441
1442impl num_traits::One for TDim {
1443 fn one() -> Self {
1444 TDim::Val(1)
1445 }
1446}
1447
1448impl ::std::iter::Sum for TDim {
1449 fn sum<I: Iterator<Item = TDim>>(iter: I) -> TDim {
1450 iter.fold(0.into(), |a, b| a + b)
1451 }
1452}
1453
1454impl<'a> ::std::iter::Sum<&'a TDim> for TDim {
1455 fn sum<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {
1456 iter.fold(0.into(), |a, b| a + b)
1457 }
1458}
1459
1460impl std::iter::Product for TDim {
1461 fn product<I: Iterator<Item = TDim>>(iter: I) -> Self {
1462 iter.fold(TDim::Val(1), |a, b| a * b)
1463 }
1464}
1465
1466impl<'a> ::std::iter::Product<&'a TDim> for TDim {
1467 fn product<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {
1468 iter.fold(1.into(), |a, b| a * b)
1469 }
1470}
1471
1472macro_rules! from_i {
1473 ($i: ty) => {
1474 impl From<$i> for TDim {
1475 fn from(v: $i) -> TDim {
1476 TDim::Val(v as _)
1477 }
1478 }
1479 impl<'a> From<&'a $i> for TDim {
1480 fn from(v: &'a $i) -> TDim {
1481 TDim::Val(*v as _)
1482 }
1483 }
1484 };
1485}
1486
1487from_i!(i32);
1488from_i!(i64);
1489from_i!(u64);
1490from_i!(isize);
1491from_i!(usize);
1492
1493impl From<Symbol> for TDim {
1494 fn from(it: Symbol) -> Self {
1495 TDim::Sym(it)
1496 }
1497}
1498
1499impl<'a> From<&'a Symbol> for TDim {
1500 fn from(it: &'a Symbol) -> Self {
1501 TDim::Sym(it.clone())
1502 }
1503}
1504
1505impl ops::Neg for TDim {
1506 type Output = Self;
1507 fn neg(self) -> Self {
1508 if let Val(v) = self { Val(-v) } else { TDim::MulInt(-1, Box::new(self)).reduce() }
1509 }
1510}
1511
1512impl<'a> ops::AddAssign<&'a TDim> for TDim {
1513 fn add_assign(&mut self, rhs: &'a TDim) {
1514 if rhs.is_zero() {
1515 } else if self.is_zero() {
1516 *self = rhs.clone();
1517 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
1518 *s += o;
1519 } else {
1520 *self = TDim::Add(vec![std::mem::take(self), rhs.clone()]).reduce()
1521 }
1522 }
1523}
1524
1525impl<I> ops::AddAssign<I> for TDim
1526where
1527 I: Into<TDim>,
1528{
1529 fn add_assign(&mut self, rhs: I) {
1530 let rhs = rhs.into();
1531 if rhs.is_zero() {
1532 } else if self.is_zero() {
1533 *self = rhs;
1534 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
1535 *s += o;
1536 } else {
1537 *self = TDim::Add(vec![std::mem::take(self), rhs]).reduce()
1538 }
1539 }
1540}
1541
1542impl<I> ops::Add<I> for TDim
1543where
1544 I: Into<TDim>,
1545{
1546 type Output = Self;
1547 fn add(mut self, rhs: I) -> Self {
1548 self += rhs;
1549 self
1550 }
1551}
1552
1553impl<'a> ops::Add<&'a TDim> for TDim {
1554 type Output = Self;
1555 fn add(mut self, rhs: &'a TDim) -> Self {
1556 self += rhs;
1557 self
1558 }
1559}
1560
1561#[allow(clippy::suspicious_op_assign_impl)]
1562impl<'a> ops::SubAssign<&'a TDim> for TDim {
1563 fn sub_assign(&mut self, rhs: &'a TDim) {
1564 if rhs.is_zero() {
1565 } else if self.is_zero() {
1566 *self = rhs.clone().neg();
1567 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
1568 *s -= o;
1569 } else {
1570 *self = TDim::Add(vec![std::mem::take(self), rhs.clone().neg()]).reduce()
1571 }
1572 }
1573}
1574
1575impl<I> ops::SubAssign<I> for TDim
1576where
1577 I: Into<TDim>,
1578{
1579 fn sub_assign(&mut self, rhs: I) {
1580 let rhs = rhs.into();
1581 if rhs.is_zero() {
1582 } else if self.is_zero() {
1583 *self = rhs.neg();
1584 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
1585 *s -= o;
1586 } else {
1587 *self = TDim::Add(vec![std::mem::take(self), rhs.neg()]).reduce()
1588 }
1589 }
1590}
1591
1592impl<I> ops::Sub<I> for TDim
1593where
1594 I: Into<TDim>,
1595{
1596 type Output = Self;
1597 fn sub(mut self, rhs: I) -> Self {
1598 self -= rhs;
1599 self
1600 }
1601}
1602
1603impl<'a> ops::Sub<&'a TDim> for TDim {
1604 type Output = Self;
1605 fn sub(mut self, rhs: &'a TDim) -> Self {
1606 self -= rhs;
1607 self
1608 }
1609}
1610
1611impl<I: Into<TDim>> ops::MulAssign<I> for TDim {
1612 fn mul_assign(&mut self, rhs: I) {
1613 let rhs = rhs.into();
1614 if self.is_one() {
1615 *self = rhs
1616 } else if rhs.is_one() {
1617 } else {
1618 *self = TDim::Mul(vec![rhs, std::mem::take(self)]).reduce()
1619 }
1620 }
1621}
1622
1623impl<'a> ops::MulAssign<&'a TDim> for TDim {
1624 fn mul_assign(&mut self, rhs: &'a TDim) {
1625 if self.is_one() {
1626 *self = rhs.clone()
1627 } else if rhs.is_one() {
1628 } else {
1629 *self = TDim::Mul(vec![std::mem::take(self), rhs.clone()]).reduce()
1630 }
1631 }
1632}
1633
1634impl<I: Into<TDim>> ops::Mul<I> for TDim {
1635 type Output = Self;
1636 fn mul(mut self, rhs: I) -> Self {
1637 self *= rhs.into();
1638 self
1639 }
1640}
1641
1642impl<'a> ops::Mul<&'a TDim> for TDim {
1643 type Output = Self;
1644 fn mul(mut self, rhs: &'a TDim) -> Self {
1645 self *= rhs;
1646 self
1647 }
1648}
1649
1650impl<I: AsPrimitive<u64> + PrimInt> ops::DivAssign<I> for TDim {
1651 fn div_assign(&mut self, rhs: I) {
1652 *self = TDim::Div(Box::new(std::mem::take(self)), rhs.as_()).reduce()
1653 }
1654}
1655
1656impl<I: AsPrimitive<u64> + PrimInt> ops::Div<I> for TDim {
1657 type Output = Self;
1658 fn div(mut self, rhs: I) -> Self {
1659 self /= rhs.as_();
1660 self
1661 }
1662}
1663
1664impl<I: AsPrimitive<u64> + PrimInt> ops::RemAssign<I> for TDim {
1665 fn rem_assign(&mut self, rhs: I) {
1666 *self += -(self.clone() / rhs.as_() * rhs.as_());
1667 }
1668}
1669
1670impl<I: AsPrimitive<u64> + PrimInt> ops::Rem<I> for TDim {
1671 type Output = Self;
1672 fn rem(mut self, rhs: I) -> Self {
1673 self %= rhs;
1674 self
1675 }
1676}
1677
1678#[cfg(test)]
1679mod tests {
1680 use super::*;
1681
1682 macro_rules! b( ($e:expr) => { Box::new($e) } );
1683
1684 lazy_static::lazy_static! {
1685 static ref table: SymbolScope = SymbolScope::default();
1686 static ref A: Symbol = table.sym("a");
1687 static ref B: Symbol = table.sym("b");
1688 static ref C: Symbol = table.sym("c");
1689 static ref D: Symbol = table.sym("d");
1690 static ref E: Symbol = table.sym("e");
1691 }
1692
1693 fn neg(a: &TDim) -> TDim {
1694 mul(-1, a)
1695 }
1696
1697 fn add(a: &TDim, b: &TDim) -> TDim {
1698 TDim::Add(vec![a.clone(), b.clone()])
1699 }
1700
1701 fn mul(a: i64, b: &TDim) -> TDim {
1702 TDim::MulInt(a, b![b.clone()])
1703 }
1704
1705 fn div(a: &TDim, b: u64) -> TDim {
1706 TDim::Div(b!(a.clone()), b)
1707 }
1708
1709 #[test]
1710 fn reduce_add() {
1711 assert_eq!(add(&A.to_dim(), &neg(&A.to_dim())).reduce(), Val(0))
1712 }
1713
1714 #[test]
1715 fn lcm_basic() {
1716 assert_eq!(Val(16).lcm(&Val(32)), Some(Val(32)));
1717 assert_eq!(Val(32).lcm(&Val(16)), Some(Val(32)));
1718 assert_eq!(Val(6).lcm(&Val(8)), Some(Val(24)));
1719 assert_eq!(Val(7).lcm(&Val(7)), Some(Val(7)));
1720 assert_eq!(Val(16).lcm(&A.to_dim()), None);
1722 }
1723
1724 #[test]
1725 fn reduce_neg_mul() {
1726 assert_eq!(neg(&mul(2, &A.to_dim())).reduce(), mul(-2, &A.to_dim()))
1727 }
1728
1729 #[test]
1730 fn reduce_cplx_ex_2() {
1731 assert_eq!(
1732 add(
1733 &add(&Val(-4), &mul(-2, &div(&A.to_dim(), 4))),
1734 &mul(-2, &mul(-1, &div(&A.to_dim(), 4)))
1735 )
1736 .reduce(),
1737 Val(-4)
1738 )
1739 }
1740
1741 #[test]
1742 fn reduce_cplx_ex_3() {
1743 assert_eq!(div(&MulInt(1, b!(MulInt(4, b!(A.to_dim())))), 4).reduce(), A.to_dim())
1744 }
1745
1746 #[test]
1747 fn reduce_cplx_ex_4() {
1748 assert_eq!(
1750 add(&div(&add(&A.to_dim(), &Val(1)), 2), &div(&add(&neg(&A.to_dim()), &Val(1)), 2))
1751 .reduce(),
1752 1.into()
1753 );
1754 }
1755
1756 #[test]
1757 fn reduce_mul_mul_1() {
1758 assert_eq!(mul(3, &mul(2, &A.to_dim())).reduce(), mul(6, &A.to_dim()))
1759 }
1760
1761 #[test]
1762 fn reduce_mul_mul_2() {
1763 assert_eq!(mul(-2, &mul(-1, &A.to_dim())).reduce(), mul(2, &A.to_dim()))
1764 }
1765
1766 #[test]
1767 fn reduce_mul_div_1() {
1768 assert_eq!(mul(2, &div(&mul(-1, &A.to_dim()), 3)).reduce(), mul(-2, &div(&A.to_dim(), 3)))
1769 }
1770
1771 #[test]
1772 fn const_and_add() {
1773 let e: TDim = 2i64.into();
1774 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 2);
1775 let e: TDim = TDim::from(2) + 3;
1776 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 5);
1777 let e: TDim = TDim::from(2) - 3;
1778 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -1);
1779 let e: TDim = -TDim::from(2);
1780 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -2);
1781 }
1782
1783 #[test]
1784 fn substitution() {
1785 let a: TDim = A.to_dim();
1786 assert_eq!(a.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 2);
1787 let e = a + 3;
1788 assert_eq!(e.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 5);
1789 }
1790
1791 #[test]
1792 fn reduce_adds() {
1793 let e: TDim = TDim::from(2) + 1;
1794 assert_eq!(e, TDim::from(3));
1795 let e: TDim = TDim::from(3) + 2;
1796 assert_eq!(e, TDim::from(5));
1797 let e: TDim = TDim::from(3) + 0;
1798 assert_eq!(e, TDim::from(3));
1799 let e: TDim = TDim::from(3) + 2 + 1;
1800 assert_eq!(e, TDim::from(6));
1801 }
1802
1803 #[test]
1804 fn reduce_muls() {
1805 let e: TDim = Val(1) * A.to_dim();
1806 assert_eq!(e, A.to_dim());
1807 let e: TDim = A.to_dim() * &B.to_dim() * 1;
1808 assert_eq!(e, A.to_dim() * &B.to_dim());
1809 }
1810
1811 #[test]
1812 fn reduce_divs() {
1813 let e: TDim = TDim::from(2) / 1;
1814 assert_eq!(e, TDim::from(2));
1815 let e: TDim = TDim::from(3) / 2;
1816 assert_eq!(e, TDim::from(1));
1817 let e: TDim = TDim::from(3) % 2;
1818 assert_eq!(e, TDim::from(1));
1819 let e: TDim = TDim::from(5) / 2;
1820 assert_eq!(e, TDim::from(2));
1821 let e: TDim = TDim::from(5) % 2;
1822 assert_eq!(e, TDim::from(1));
1823 }
1824
1825 #[test]
1826 fn reduce_div_bug_0() {
1827 let e1: TDim = (A.to_dim() + 23) / 2 - 1;
1828 let e2: TDim = (A.to_dim() + 21) / 2;
1829 assert_eq!(e1, e2);
1830 }
1831
1832 #[test]
1833 fn reduce_div_bug_1() {
1834 let e1: TDim = (A.to_dim() + -1) / 2;
1835 let e2: TDim = (A.to_dim() + 1) / 2 - 1;
1836 assert_eq!(e1, e2);
1837 }
1838
1839 #[test]
1840 fn reduce_div_bug_2() {
1841 let e1: TDim = ((A.to_dim() + 1) / 2 + 1) / 2;
1842 let e2: TDim = (A.to_dim() + 3) / 4;
1843 assert_eq!(e1, e2);
1844 }
1845
1846 #[test]
1847 fn divide_multiple_plus_remainder() {
1848 let scope = SymbolScope::default().with_assertion("S>=0").unwrap();
1850 let s = scope.sym("S");
1851
1852 let e: TDim = (s.to_dim() * 2 + 1) / 2;
1854 assert_eq!(e.simplify(), s.to_dim());
1855
1856 let e: TDim = (s.to_dim() * 2 + 1) / 2 - 1;
1858 assert_eq!(e.simplify(), s.to_dim() - 1);
1859
1860 let e: TDim = (s.to_dim() * 2 - 1) / 2;
1862 assert_eq!(e.simplify(), s.to_dim() - 1);
1863
1864 let e: TDim = (s.to_dim() * 4 + 3) / 2;
1866 assert_eq!(e.simplify(), s.to_dim() * 2 + 1);
1867 }
1868
1869 #[test]
1870 fn divide_multiple_plus_remainder_no_assertion() {
1871 let scope = SymbolScope::default();
1878 let s = scope.sym("S");
1879 let e: TDim = (s.to_dim() * 2 + 1) / 2;
1880 assert_ne!(e.simplify(), s.to_dim());
1881 }
1882
1883 #[test]
1884 fn modulo_div_is_zero() {
1885 let scope = SymbolScope::default();
1888 let s = scope.sym("S");
1889 let e: TDim = (s.to_dim() - s.to_dim() / 2 * 2) / 2;
1891 assert_eq!(e.simplify(), TDim::Val(0));
1892 let a = s.to_dim() + 1;
1895 let e2: TDim = (a.clone() - a.clone() / 2 * 2) / 2;
1896 assert_eq!(e2.simplify(), TDim::Val(0));
1897 }
1898
1899 #[test]
1900 fn reduce_div_bug_3() {
1901 let e1: TDim = (A.to_dim() / 2) * -4;
1902 let e2: TDim = (A.to_dim() / 2) * -4 / 1;
1903 assert_eq!(e1, e2);
1904 }
1905
1906 #[test]
1907 fn reduce_mul_div() {
1908 let e: TDim = A.to_dim() * 2 / 2;
1909 assert_eq!(e, A.to_dim());
1910 }
1911
1912 #[test]
1913 fn expand_polynomial_two_add_factors() {
1914 let a = A.to_dim();
1919 let b = B.to_dim();
1920 let lhs = (a.clone() + a.clone() * &b * 2) * (TDim::from(1) + &b);
1921 let rhs = a.clone() * (TDim::from(1) + &b) * (TDim::from(1) + b.clone() * 2);
1922 assert_eq!(lhs.expand_polynomial(), rhs.expand_polynomial());
1923 }
1924
1925 #[test]
1926 fn reduce_div_mul() {
1927 let e: TDim = A.to_dim() / 2 * 2;
1928 assert_ne!(e, A.to_dim());
1929 }
1930
1931 #[test]
1932 fn reduce_add_div() {
1933 let e: TDim = A.to_dim() / 2 + 1;
1934 assert_eq!(e, ((A.to_dim() + 2) / 2));
1935 }
1936
1937 #[test]
1938 fn reduce_neg_mul_() {
1939 let e: TDim = TDim::from(1) - A.to_dim() * 2;
1940 assert_eq!(e, TDim::from(1) + A.to_dim() * -2);
1941 }
1942
1943 #[test]
1944 fn reduce_add_rem_1() {
1945 assert_eq!(((A.to_dim() + 4) % 2), (A.to_dim() % 2));
1946 }
1947
1948 #[test]
1949 fn reduce_add_rem_2() {
1950 assert_eq!(((A.to_dim() - 4) % 2), (A.to_dim() % 2));
1951 }
1952
1953 #[test]
1954 fn reduce_rem_div() {
1955 let e: TDim = A.to_dim() % 2 / 2;
1956 assert_eq!(e, TDim::from(0));
1957 }
1958
1959 #[test]
1960 fn conv2d_ex_1() {
1961 let e = (TDim::from(1) - 1 + 1).div_ceil(1);
1962 assert_eq!(e, TDim::from(1));
1963 }
1964
1965 #[test]
1966 fn conv2d_ex_2() {
1967 let e = (A.to_dim() - 3 + 1).div_ceil(1);
1968 assert_eq!(e, A.to_dim() + -2);
1969 }
1970
1971 #[test]
1972 fn extract_int_gcd_from_muls() {
1973 let term = (A.to_dim() + 1) / 4;
1974 let mul = (term.clone() * 24 - 24) * (term.clone() * 2 - 2);
1975 let target = (term.clone() - 1) * (term.clone() - 1) * 48;
1976 assert_eq!(mul, target);
1977 }
1978
1979 #[test]
1980 fn equality_of_muls() {
1981 let term = (A.to_dim() + 1) / 4;
1982 let mul1 = (term.clone() * 2 - 3) * (term.clone() - 1);
1983 let mul2 = (term.clone() - 1) * (term.clone() * 2 - 3);
1984 assert_eq!(mul1, mul2);
1985 }
1986
1987 #[test]
1988 fn factorize_complex_expr_times_int() {
1989 let term = (A.to_dim() + 1) / 4;
1990 let e = term.clone() * 2 - &term - 1;
1991 assert_eq!(e, term - 1);
1992 }
1993
1994 #[test]
1995 fn broadcast_over_min() {
1996 for a in 1..5 {
2002 for b in 1..5 {
2003 if b > 1 && a > b {
2004 assert!(a.broadcast(a.min(b)).is_err());
2005 } else {
2006 assert_eq!(a.broadcast(a.min(b)).unwrap(), a);
2007 }
2008 }
2009 }
2010 }
2011
2012 #[test]
2013 fn min_ints_1() {
2014 assert_eq!(2.to_dim().mini(1.to_dim()), 1.to_dim());
2015 }
2016
2017 #[test]
2018 fn min_ints_2() {
2019 assert_eq!(1.to_dim().mini(2.to_dim()), 1.to_dim());
2020 }
2021
2022 #[test]
2023 fn min_same() {
2024 assert_eq!(A.to_dim().mini(A.to_dim()), A.to_dim());
2025 }
2026
2027 #[test]
2028 fn min_noop() {
2029 assert_eq!(A.to_dim().mini(1.to_dim()), A.to_dim().mini(1.to_dim()));
2030 }
2031
2032 #[test]
2033 fn min_diff_1() {
2034 assert_eq!((A.to_dim() + 1).mini(A.to_dim() + 2), A.to_dim() + 1);
2035 }
2036
2037 #[test]
2038 fn slope_0() {
2039 assert_eq!(12.to_dim().guess_slope(&A), (0, 1));
2040 }
2041
2042 #[test]
2043 fn slope_1() {
2044 assert_eq!(A.to_dim().guess_slope(&A), (1, 1));
2045 }
2046
2047 #[test]
2048 fn slope_2() {
2049 assert_eq!((A.to_dim() * 2).guess_slope(&A), (2, 1));
2050 }
2051
2052 #[test]
2053 fn slope_3() {
2054 assert_eq!((A.to_dim() * 2 + A.to_dim() / 2).guess_slope(&A), (5, 2));
2055 }
2056
2057 #[test]
2058 fn slope_4() {
2059 assert_eq!((A.to_dim()).guess_slope(&B), (0, 1));
2060 }
2061
2062 #[test]
2063 fn slope_5() {
2064 assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));
2065 assert_eq!((A.to_dim() + 1).guess_slope(&B), (0, 1));
2066 }
2067
2068 #[test]
2069 fn slope_6() {
2070 assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));
2071 assert_eq!((A.to_dim() + B.to_dim()).guess_slope(&B), (1, 1));
2072 }
2073
2074 #[test]
2075 fn min_0() -> TractResult<()> {
2076 let symbols = SymbolScope::default();
2077 assert_eq!(
2078 symbols.parse_tdim("min(S+3, S+2)").unwrap().simplify(),
2079 symbols.parse_tdim("S+2").unwrap(),
2080 );
2081 Ok(())
2082 }
2083
2084 #[test]
2085 fn commutative_mul_parens() -> TractResult<()> {
2086 let symbols = SymbolScope::default();
2087 assert_eq!(
2088 symbols.parse_tdim("A*(B*C)").unwrap().simplify(),
2089 symbols.parse_tdim("(B*A)*C").unwrap().simplify(),
2090 );
2091 Ok(())
2092 }
2093
2094 #[test]
2095 fn commutative_in_nemo_parakeet_model() -> TractResult<()> {
2096 let symbols = SymbolScope::default();
2097 assert_eq!(
2098 symbols
2099 .parse_tdim("8*(1+-1*max(0,5000+-1*(S+7)/8)+max(0,4999+(S+7)/8))*((B)*((S+7)/8))")
2100 .unwrap()
2101 .simplify(),
2102 symbols
2103 .parse_tdim("8*((B)*(1+-1*max(0,5000+-1*(S+7)/8)+max(0,4999+(S+7)/8)))*((S+7)/8)")
2104 .unwrap()
2105 .simplify(),
2106 );
2107 Ok(())
2108 }
2109
2110 #[test]
2111 fn commutative_mul_parens_deep() -> TractResult<()> {
2112 let symbols = SymbolScope::default();
2113 let deep_tdim = Mul(vec![
2114 Mul(vec![Mul(vec![Mul(vec![A.to_dim(), B.to_dim()]), C.to_dim()]), D.to_dim()]),
2115 E.to_dim(),
2116 ])
2117 .simplify();
2118 assert_eq!(deep_tdim, symbols.parse_tdim("a*b*c*d*e").unwrap().simplify());
2119 Ok(())
2120 }
2121
2122 #[test]
2125 fn ge_concrete_true() {
2126 assert_eq!(Ge(b!(Val(5)), b!(Val(3))).reduce(), Val(1));
2127 }
2128
2129 #[test]
2130 fn ge_concrete_false() {
2131 assert_eq!(Ge(b!(Val(2)), b!(Val(3))).reduce(), Val(0));
2132 }
2133
2134 #[test]
2135 fn lt_concrete_true() {
2136 assert_eq!(Ge(b!(Val(3)), b!(Val(3))).reduce(), Val(1));
2138 }
2139
2140 #[test]
2141 fn lt_concrete_false() {
2142 assert_eq!(Ge(b!(Val(3)), b!(Val(6))).reduce(), Val(0));
2144 }
2145
2146 #[test]
2147 fn eq_concrete_true() {
2148 assert_eq!(Eq(b!(Val(3)), b!(Val(3))).reduce(), Val(1));
2149 }
2150
2151 #[test]
2152 fn eq_concrete_false() {
2153 assert_eq!(Eq(b!(Val(3)), b!(Val(4))).reduce(), Val(0));
2154 }
2155
2156 #[test]
2157 fn not_val_0() {
2158 assert_eq!((Val(1) - Val(0)).reduce(), Val(1));
2160 }
2161
2162 #[test]
2163 fn not_val_1() {
2164 assert_eq!((Val(1) - Val(1)).reduce(), Val(0));
2166 }
2167
2168 #[test]
2169 fn not_lt_becomes_ge() {
2170 let s = SymbolScope::default();
2172 let t = s.sym("T");
2173 let x1 = s.sym("x1");
2174 let expr = Val(1) - Ge(b!(Sym(t.clone())), b!(Sym(x1.clone()) + Val(1)));
2176 let at_boundary = expr.substitute(&x1, &Sym(t.clone())).unwrap().simplify();
2177 assert_eq!(at_boundary, Val(1));
2178 }
2179
2180 #[test]
2181 fn eq_with_assertion_proves_false() {
2182 let s = SymbolScope::default();
2184 s.add_assertion("T >= 1").unwrap();
2185 let t = s.sym("T");
2186 let expr = Eq(b!(Sym(t)), b!(Val(0)));
2187 assert_eq!(expr.simplify(), Val(0));
2188 }
2189
2190 #[test]
2191 fn ge_coord_at_extremes() {
2192 let s = SymbolScope::default();
2194 s.add_assertion("T >= 1").unwrap();
2195 let t = s.sym("T");
2196 let x1 = s.sym("x1");
2197 let expr = Ge(b!(Sym(x1.clone())), b!(Sym(t.clone())));
2198 let at_max = expr.substitute(&x1, &(Sym(t.clone()) - Val(1))).unwrap().simplify();
2201 assert_eq!(at_max, Val(0));
2202 }
2203
2204 #[test]
2205 fn eval_to_i64_new_variants() {
2206 use super::super::sym::SymbolValues;
2207 let sv = SymbolValues::default();
2208 assert_eq!(Ge(b!(Val(5)), b!(Val(3))).eval_to_i64(&sv).unwrap(), 1);
2209 assert_eq!(Ge(b!(Val(3)), b!(Val(5))).eval_to_i64(&sv).unwrap(), 0);
2210 assert_eq!(Eq(b!(Val(3)), b!(Val(3))).eval_to_i64(&sv).unwrap(), 1);
2211 assert_eq!(Eq(b!(Val(3)), b!(Val(4))).eval_to_i64(&sv).unwrap(), 0);
2212 }
2213
2214 #[test]
2215 fn eq_boolean_simplifies() {
2216 let s = SymbolScope::default();
2217 s.add_assertion("cw >= 0").unwrap();
2218 s.add_assertion("cw <= 1").unwrap();
2219 let cw = s.sym("cw");
2220 assert_eq!(Eq(b!(Val(1) - Sym(cw.clone())), b!(Val(0))).simplify(), Sym(cw.clone()));
2222 assert_eq!(Eq(b!(Sym(cw.clone())), b!(Val(0))).simplify(), Val(1) - Sym(cw.clone()));
2224 assert_eq!(Eq(b!(Sym(cw.clone())), b!(Val(1))).simplify(), Sym(cw.clone()));
2226 assert_eq!(Eq(b!(Val(1) - Sym(cw.clone())), b!(Val(1))).simplify(), Val(1) - Sym(cw));
2228 }
2229
2230 #[test]
2231 fn eq_boolean_mul_of_ge() {
2232 let s = SymbolScope::default();
2235 let x = s.sym("x");
2236 let product =
2237 Mul(vec![Ge(b!(Val(2)), b!(Sym(x.clone()))), Ge(b!(Sym(x.clone())), b!(Val(0)))]);
2238 let eq = Eq(b!(product.clone()), b!(Val(0)));
2239 assert_eq!(eq.simplify(), Val(1) - product);
2240 }
2241
2242 #[test]
2243 fn min_1_max_0_sym() {
2244 let s = SymbolScope::default();
2246 let x = s.sym("X");
2247 let expr = Min(vec![Val(1), Max(vec![Val(0), Sym(x)])]);
2248 let simplified = expr.simplify();
2249 eprintln!("simplified: {simplified}");
2250 assert!(format!("{simplified}").contains("min"), "Min dropped: {simplified}");
2251 }
2252
2253 #[test]
2254 fn min_preserved_in_subtraction_parts() {
2255 let s = SymbolScope::default();
2257 let t = s.sym("T");
2258 let p = s.sym("P");
2259 let ss = s.sym("S");
2260
2261 let cum_after =
2262 Max(vec![Val(0), (Sym(t.clone()) + Val(1)) * Sym(p.clone()) - Sym(ss.clone())]);
2263 let min_after = Min(vec![Val(1), cum_after.clone()]);
2264 let simplified = min_after.simplify();
2265 eprintln!("min_after simplified: {simplified}");
2266 assert!(format!("{simplified}").contains("min"), "Min wrapper was dropped: {simplified}");
2268 }
2269
2270 #[test]
2271 fn min_preserved_in_subtraction() {
2272 let s = SymbolScope::default();
2275 let t = s.sym("T");
2276 let p = s.sym("P");
2277 let ss = s.sym("S");
2278
2279 let cum_after =
2280 Max(vec![Val(0), (Sym(t.clone()) + Val(1)) * Sym(p.clone()) - Sym(ss.clone())]);
2281 let cum_before = Max(vec![Val(0), Sym(t.clone()) * Sym(p.clone()) - Sym(ss.clone())]);
2282
2283 let ap = Min(vec![Val(1), cum_after.clone()]) - Min(vec![Val(1), cum_before.clone()]);
2284 let simplified = ap.simplify();
2285
2286 use super::super::sym::SymbolValues;
2288 let sv = SymbolValues::default().with(&t, 1).with(&p, 4).with(&ss, 3);
2289 assert_eq!(simplified.eval_to_i64(&sv).unwrap(), 0, "simplified: {simplified}");
2290
2291 let sv = SymbolValues::default().with(&t, 0).with(&p, 4).with(&ss, 3);
2293 assert_eq!(simplified.eval_to_i64(&sv).unwrap(), 1, "simplified: {simplified}");
2294
2295 let sv = SymbolValues::default().with(&t, 0).with(&p, 1).with(&ss, 1);
2297 assert_eq!(simplified.eval_to_i64(&sv).unwrap(), 0, "simplified: {simplified}");
2298 }
2299
2300 #[test]
2301 fn mul_neg_b_by_8() {
2302 let s = SymbolScope::default();
2303 let b = Sym(s.sym("B"));
2304 let a = Mul(vec![Val(8), MulInt(-1, Box::new(b.clone()))]);
2306 let c = MulInt(-8, Box::new(b.clone()));
2307 let a_s = a.simplify();
2308 let c_s = c.simplify();
2309 assert_eq!(a_s, c_s, "8*(-1*B) should simplify the same as -8*B");
2310 }
2311
2312 #[test]
2316 fn reduce_div_by_common_factor_with_divisor() {
2317 let lhs = (A.to_dim() * 14 + 6) / 8;
2318 let rhs = (A.to_dim() * 7 + 3) / 4;
2319 assert_eq!(lhs, rhs);
2320 }
2321
2322 #[test]
2325 fn reduce_div_when_factor_equals_divisor() {
2326 let lhs = (A.to_dim() * 4 + 8) / 4;
2327 let rhs = A.to_dim() + 2;
2328 assert_eq!(lhs, rhs);
2329 }
2330
2331 #[test]
2334 fn no_reduce_when_terms_coprime_with_divisor() {
2335 let e = (A.to_dim() * 7 + 3) / 4;
2336 match &e {
2339 Div(_, q) => assert_eq!(*q, 4),
2340 other => panic!("expected Div(_, 4), got {other:?}"),
2341 }
2342 }
2343
2344 #[test]
2348 fn no_reduce_when_sym_has_implicit_unit_coefficient() {
2349 let e = (A.to_dim() + 4) / 2;
2351 let sv2 = SymbolValues::default().with(&A, 2);
2354 let sv4 = SymbolValues::default().with(&A, 4);
2355 assert_eq!(e.eval_to_i64(&sv2).unwrap(), 3);
2356 assert_eq!(e.eval_to_i64(&sv4).unwrap(), 4);
2357 }
2358}