1use crate::dim::Assertion;
2use crate::internal::*;
3
4use super::{sym::*, DimLike};
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(TDim),
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#[derive(Clone, PartialEq, Eq, Hash, Debug)]
34pub enum TDim {
35 Val(i64),
36 Sym(Symbol),
37 Add(Vec<TDim>),
38 Mul(Vec<TDim>),
39 MulInt(i64, Box<TDim>),
40 Div(Box<TDim>, u64),
41 Broadcast(Vec<TDim>),
42 Min(Vec<TDim>),
43 Max(Vec<TDim>),
44}
45
46use TDim::*;
47
48fn tdim_lexi_order(a: &TDim, b: &TDim) -> Ordering {
49 match (a, b) {
50 (Sym(a), Sym(b)) => a.cmp(b),
51 (Val(a), Val(b)) => a.cmp(b),
52 (Add(a), Add(b))
53 | (Mul(a), Mul(b))
54 | (Broadcast(a), Broadcast(b))
55 | (Min(a), Min(b))
56 | (Max(a), Max(b)) => a.len().cmp(&b.len()).then(
57 a.iter()
58 .zip(b.iter())
59 .fold(Ordering::Equal, |acc, (a, b)| acc.then_with(|| tdim_lexi_order(a, b))),
60 ),
61 (MulInt(p, d), MulInt(q, e)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),
62 (Div(d, p), Div(e, q)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),
63 (Sym(_), _) => Ordering::Less,
64 (_, Sym(_)) => Ordering::Greater,
65 (Val(_), _) => Ordering::Less,
66 (_, Val(_)) => Ordering::Greater,
67 (Add(_), _) => Ordering::Less,
68 (_, Add(_)) => Ordering::Greater,
69 (Mul(_), _) => Ordering::Less,
70 (_, Mul(_)) => Ordering::Greater,
71 (MulInt(_, _), _) => Ordering::Less,
72 (_, MulInt(_, _)) => Ordering::Greater,
73 (Broadcast(_), _) => Ordering::Less,
74 (_, Broadcast(_)) => Ordering::Greater,
75 (Min(_), _) => Ordering::Less,
76 (_, Min(_)) => Ordering::Greater,
77 (Max(_), _) => Ordering::Less,
78 (_, Max(_)) => Ordering::Greater,
79 }
80}
81
82impl fmt::Display for TDim {
83 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
84 match &self {
85 Sym(sym) => write!(fmt, "{sym}"),
86 Val(it) => write!(fmt, "{it}"),
87 Add(it) => write!(fmt, "{}", it.iter().map(|x| format!("{x}")).join("+")),
88 Mul(it) => write!(fmt, "{}", it.iter().map(|x| format!("({x})")).join("*")),
89 Broadcast(it) => write!(fmt, "{}", it.iter().map(|x| format!("({x})")).join("#")),
90 Min(it) => write!(fmt, "min({})", it.iter().map(|x| format!("{x}")).join(",")),
91 Max(it) => write!(fmt, "max({})", it.iter().map(|x| format!("{x}")).join(",")),
92 MulInt(a, b) => write!(fmt, "{a}*{b}"),
93 Div(a, b) => write!(fmt, "({a})/{b}"),
94 }
95 }
96}
97
98impl TDim {
99 #[inline]
100 pub fn is_one(&self) -> bool {
101 matches!(self, Val(1))
102 }
103
104 #[inline]
105 pub fn to_i64(&self) -> TractResult<i64> {
106 if let Val(v) = self {
107 Ok(*v)
108 } else {
109 Err(TooEarly::UndeterminedSymbol(self.clone()).into())
110 }
111 }
112
113 #[inline]
114 pub fn as_i64(&self) -> Option<i64> {
115 if let Val(v) = self {
116 Some(*v)
117 } else {
118 None
119 }
120 }
121
122 pub fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64> {
123 match self {
124 Sym(sym) => {
125 let Some(v) = values.get(sym) else {
126 bail!(TooEarly::UndeterminedSymbol(self.clone()))
127 };
128 Ok(v)
129 }
130 Val(v) => Ok(*v),
131 Add(terms) => {
132 terms.iter().try_fold(0, |acc, it| it.eval_to_i64(values).map(|x| acc + x))
133 }
134 Mul(terms) => {
135 terms.iter().try_fold(1, |acc, it| it.eval_to_i64(values).map(|x| acc * x))
136 }
137 Min(terms) => terms
138 .iter()
139 .try_fold(i64::MAX, |acc, it| it.eval_to_i64(values).map(|x| acc.min(x))),
140 Max(terms) => terms
141 .iter()
142 .try_fold(i64::MIN, |acc, it| it.eval_to_i64(values).map(|x| acc.max(x))),
143 Broadcast(terms) => terms.iter().try_fold(1i64, |acc, it| {
144 it.eval_to_i64(values)
145 .and_then(|x| ((acc as usize).broadcast(x as usize)).map(|x| x as i64))
146 }),
147 Div(a, q) => Ok(a.eval_to_i64(values)? / *q as i64),
148 MulInt(p, a) => Ok(a.eval_to_i64(values)? * *p),
149 }
150 }
151
152 pub fn eval(&self, values: &SymbolValues) -> TDim {
153 match self {
154 Sym(sym) => values.get(sym).map(Val).unwrap_or_else(|| Sym(sym.clone())),
155 Val(v) => Val(*v),
156 Add(terms) => terms.iter().fold(Val(0), |acc, it| -> TDim { acc + it.eval(values) }),
157 Mul(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim { acc * it.eval(values) }),
158 Min(terms) => {
159 terms.iter().fold(Val(i64::MAX), |acc, it| -> TDim { acc.mini(it.eval(values)) })
160 }
161 Max(terms) => {
162 terms.iter().fold(Val(i64::MIN), |acc, it| -> TDim { acc.maxi(it.eval(values)) })
163 }
164 Broadcast(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim {
165 acc.broadcast(it.eval(values)).unwrap_or_else(|_| self.clone())
166 }),
167 Div(a, q) => a.eval(values) / *q as i64,
168 MulInt(p, a) => a.eval(values) * *p,
169 }
170 }
171
172 pub fn eval_with_scenario(&self, scenario: &str) -> TDim {
173 if let Val(v) = self {
174 return Val(*v);
175 }
176 let scope = self.find_scope().unwrap();
177 let scope = scope.0;
178 let locked = scope.lock();
179 let scope = locked.borrow();
180 self.clone().simplify_rec(&scope, Some(scenario))
181 }
182
183 pub fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self> {
184 match self {
185 Sym(sym) => Ok(if sym == from { to.clone() } else { self.clone() }),
186 Val(v) => Ok(Val(*v)),
187 Add(terms) => terms.iter().try_fold(Val(0), |acc, it| -> TractResult<TDim> {
188 Ok(acc + it.substitute(from, to)?)
189 }),
190 Mul(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {
191 Ok(acc * it.substitute(from, to)?)
192 }),
193 Broadcast(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {
194 acc.broadcast(it.substitute(from, to)?)
195 }),
196 Min(terms) => terms.iter().try_fold(Val(i64::MAX), |acc, it| -> TractResult<TDim> {
197 Ok(acc.mini(it.substitute(from, to)?))
198 }),
199 Max(terms) => terms.iter().try_fold(Val(i64::MIN), |acc, it| -> TractResult<TDim> {
200 Ok(acc.maxi(it.substitute(from, to)?))
201 }),
202 Div(a, q) => Ok(a.substitute(from, to)? / *q as i64),
203 MulInt(p, a) => Ok(a.substitute(from, to)? * *p),
204 }
205 }
206
207 pub fn reduce(self) -> TDim {
208 self.simplify()
209 .wiggle()
210 .into_iter()
211 .sorted_by(tdim_lexi_order)
212 .unique()
213 .map(|e| e.simplify())
214 .min_by_key(|e| e.cost())
215 .unwrap()
216 }
217
218 fn cost(&self) -> usize {
219 use self::TDim::*;
220 match self {
221 Sym(_) | Val(_) => 1,
222 Add(terms) => 2 * terms.iter().map(TDim::cost).sum::<usize>(),
223 Mul(terms) => 3 * terms.iter().map(TDim::cost).sum::<usize>(),
224 Broadcast(terms) => 4 * terms.iter().map(TDim::cost).sum::<usize>(),
225 Min(terms) | Max(terms) => 5 * terms.iter().map(TDim::cost).sum::<usize>(),
226 Div(a, _) => 3 * a.cost(),
227 MulInt(_, a) => 2 * a.cost(),
228 }
229 }
230
231 fn wiggle(&self) -> Vec<TDim> {
232 use self::TDim::*;
233 match self {
234 Sym(_) | Val(_) | Mul(_) | Broadcast(_) | Min(_) | Max(_) => vec![self.clone()],
235 Add(terms) => {
236 let mut forms = vec![];
237 let sub_exprs = terms.iter().map(|e| e.wiggle()).multi_cartesian_product();
238
239 fn first_div_term(terms: &[TDim]) -> Option<(usize, &TDim, u64)> {
240 terms.iter().enumerate().find_map(|(index, t)| match t {
241 Div(numerator, quotient) => Some((index, &**numerator, *quotient)),
242 _ => None,
243 })
244 }
245
246 fn generate_new_numerator(
247 div_index: usize,
248 numerator: &TDim,
249 quotient: u64,
250 expr: &[TDim],
251 ) -> Vec<TDim> {
252 expr.iter()
253 .enumerate()
254 .map(|(index, term)| {
255 if index == div_index {
256 numerator.clone()
257 } else {
258 MulInt(quotient as i64, Box::new(term.clone()))
259 }
260 })
261 .collect()
262 }
263
264 for expr in sub_exprs {
265 if let Some((div_index, numerator, quotient)) = first_div_term(&expr) {
266 let new_numerator =
267 generate_new_numerator(div_index, numerator, quotient, &expr);
268 forms.push(Div(Box::new(Add(new_numerator)), quotient))
269 }
270
271 forms.push(Add(expr));
272 }
273 forms
274 }
275 MulInt(p, a) => a.wiggle().into_iter().map(|a| MulInt(*p, b!(a))).collect(),
276 Div(a, q) => {
277 let mut forms = vec![];
278 for num in a.wiggle() {
279 if let Add(terms) = &num {
280 let (integer, non_integer): (Vec<_>, Vec<_>) =
281 terms.iter().cloned().partition(|a| a.gcd() % q == 0);
282 let mut new_terms = integer.iter().map(|i| i.div(*q)).collect::<Vec<_>>();
283 if non_integer.len() > 0 {
284 new_terms.push(Div(b!(Add(non_integer)), *q));
285 }
286 forms.push(Add(new_terms))
287 }
288 forms.push(Div(b!(num), *q))
289 }
290 forms
291 }
292 }
293 }
294
295 fn find_any_sym(tdim: &TDim) -> Option<&Symbol> {
296 match tdim {
297 Val(_) => None,
298 Sym(s) => Some(s),
299 Add(terms) | Mul(terms) | Min(terms) | Max(terms) | Broadcast(terms) => {
300 terms.iter().find_map(Self::find_any_sym)
301 }
302 MulInt(_, t) | Div(t, _) => Self::find_any_sym(t),
303 }
304 }
305
306 pub fn find_scope(&self) -> Option<SymbolScope> {
307 Self::find_any_sym(self).and_then(|s| s.scope().clone())
308 }
309
310 pub fn simplify(self) -> TDim {
311 use self::TDim::*;
312 if let Ok(v) = self.eval_to_i64(&SymbolValues::default()) {
313 return Val(v);
314 }
315 let Some(scope) = self.find_scope() else {
316 return self;
317 };
318 let scope = scope.0;
319 let locked = scope.lock();
320 let scope = locked.borrow();
321 let it = self.simplify_rec(&scope, None);
322 let mut current: Option<TDim> = None;
323 for scenario in scope.scenarios() {
324 let v = it.clone().simplify_rec(&scope, Some(scenario));
325 if current.is_some_and(|c| c != v) {
326 return it;
327 } else {
328 current = Some(v);
329 }
330 }
331 current.unwrap_or(it)
332 }
333
334 fn simplify_rec(self, scope: &SymbolScopeData, scenario: Option<&str>) -> TDim {
335 match self {
336 Add(mut terms) => {
337 #[allow(clippy::mutable_key_type)]
338 let mut simplified_terms: HashMap<TDim, i64> = HashMap::new();
339 while let Some(term) = terms.pop() {
341 let simplified = term.simplify_rec(scope, scenario);
342 match simplified {
343 Val(0) => {} Add(members) => {
345 terms.extend(members);
346 continue;
347 }
348 Val(value) => *simplified_terms.entry(Val(1)).or_insert(0) += value,
349 MulInt(value, factor) => {
350 *simplified_terms.entry((*factor).clone()).or_insert(0) += value;
351 }
352 n => *simplified_terms.entry(n).or_insert(0) += 1,
353 };
354 }
355
356 pub fn evaluate_count(term: TDim, count: i64) -> Option<TDim> {
357 match count {
358 0 => None,
359 _ if term == TDim::Val(1) => Some(TDim::Val(count)),
360 1 => Some(term),
361 _ => Some(TDim::MulInt(count, Box::new(term))),
362 }
363 }
364
365 let mut members: Vec<TDim> = simplified_terms
366 .into_iter()
367 .filter_map(|(term, count)| evaluate_count(term, count))
368 .collect();
369 members.sort_by(tdim_lexi_order);
370
371 match members.len() {
372 0 => TDim::Val(0),
373 1 => members.into_iter().next().unwrap(),
374 _ => TDim::Add(members),
375 }
376 }
377 Mul(terms) => {
378 let mut gcd = Mul(terms.clone()).gcd() as i64;
379 if gcd == 0 {
380 return Val(0);
381 }
382 let mut terms = if gcd != 1 {
383 terms
384 .into_iter()
385 .map(|t| {
386 let gcd = t.gcd();
387 (t / gcd).simplify_rec(scope, scenario)
388 })
389 .collect()
390 } else {
391 terms
392 };
393 if terms.iter().filter(|t| t == &&Val(-1)).count() % 2 == 1 {
394 gcd = -gcd;
395 }
396 terms.retain(|t| !t.is_one() && t != &Val(-1));
397 terms.sort_by(tdim_lexi_order);
398 match (gcd, terms.len()) {
399 (_, 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))), }
407 }
408 MulInt(coef, expr) => {
409 match *expr {
410 MulInt(c2, inner) => {
411 return MulInt(coef * c2, inner).simplify_rec(scope, scenario)
412 }
413 Val(v) => return Val(coef * v),
414 _ => {}
415 }
416
417 let simplified = expr.simplify_rec(scope, scenario);
418 match (coef, simplified) {
419 (0, _) => Val(0), (1, s) => s, (_, Add(terms)) => Add(terms
422 .into_iter()
423 .map(|term| MulInt(coef, Box::new(term)).simplify_rec(scope, scenario))
424 .collect()), (c, Val(v)) => Val(c * v), (c, MulInt(v, inner)) => MulInt(c * v, inner), (_, s) => MulInt(coef, Box::new(s)), }
429 }
430 Div(a, q) => {
431 if q == 1 {
432 return a.simplify_rec(scope, scenario);
433 } else if let Div(a, q2) = *a {
434 return Div(a, q * q2).simplify_rec(scope, scenario);
435 }
436 let a = a.simplify_rec(scope, scenario);
437 if let Val(a) = a {
438 Val(a / q as i64)
439 } else if let MulInt(-1, a) = a {
440 MulInt(-1, b!(Div(a, q)))
441 } else if let Add(mut terms) = a {
442 if terms.iter().any(|t| {
443 if let MulInt(-1, s) = t {
444 matches!(&**s, Sym(_))
445 } else {
446 false
447 }
448 }) {
449 MulInt(
450 -1,
451 b!(Div(
452 b!(Add(terms.into_iter().map(|t| MulInt(-1, b!(t))).collect())
453 .simplify_rec(scope, scenario)),
454 q
455 )),
456 )
457 } else if let Some(v) =
458 terms.iter().find_map(|t| if let Val(v) = t { Some(*v) } else { None })
459 {
460 let offset = if v >= q as i64 {
461 Some(v / q as i64)
462 } else if v < 0 {
463 Some(-Integer::div_ceil(&-v, &(q as i64)))
464 } else {
465 None
466 };
467 if let Some(val) = offset {
468 terms.push(Val(-val * q as i64));
469 Add(vec![
470 Val(val),
471 Div(b!(Add(terms).simplify_rec(scope, scenario)), q),
472 ])
473 } else {
474 Div(b!(Add(terms)), q)
475 }
476 } else {
477 Div(b!(Add(terms)), q)
478 }
479 } else if let MulInt(p, a) = a {
480 if p == q as i64 {
481 a.simplify()
482 } else {
483 let gcd = p.abs().gcd(&(q as i64));
484 if gcd == p {
485 Div(a, q / gcd as u64)
486 } else if gcd == q as i64 {
487 MulInt(p / gcd, a)
488 } else if gcd > 1 {
489 Div(b!(MulInt(p / gcd, a)), q / gcd as u64)
490 .simplify_rec(scope, scenario)
491 } else {
492 Div(b!(MulInt(p, a)), q)
493 }
494 }
495 } else {
496 Div(b!(a), q)
497 }
498 }
499 Broadcast(terms) => {
500 let mut terms: Vec<TDim> = terms
501 .iter()
502 .map(|s| s.clone().simplify_rec(scope, scenario))
503 .flat_map(|t| if let Broadcast(t) = t { t } else { vec![t] })
504 .filter(|t| !t.is_one())
505 .sorted_by(tdim_lexi_order)
506 .dedup()
507 .collect_vec();
508 if terms.len() == 0 {
509 Val(1)
510 } else if terms.len() == 1 {
511 terms.remove(0)
512 } else {
513 Broadcast(terms)
514 }
515 }
516
517 Min(terms) => {
518 let mut flatten: Vec<TDim> = terms
519 .into_iter()
520 .map(|t| t.simplify_rec(scope, scenario))
521 .flat_map(|t| if let Min(t) = t { t } else { vec![t] })
522 .sorted_by(tdim_lexi_order)
523 .dedup()
524 .collect();
525 #[allow(clippy::mutable_key_type)]
526 let mut redundant = HashSet::<TDim>::default();
527 for pair in flatten.iter().permutations(2) {
528 let (a, b) = (pair[0], pair[1]);
529 if redundant.contains(a) || redundant.contains(b) {
530 continue;
531 }
532 let diff = a.clone() - b;
533 if diff.as_i64().is_some_and(|i| i >= 0) || scope.prove_positive_or_zero(&diff)
534 {
535 redundant.insert(a.clone());
536 }
537 }
538 flatten.retain(|t| !redundant.contains(t));
539 if flatten.len() == 0 {
540 i64::MAX.to_dim()
541 } else if flatten.len() == 1 {
542 flatten.into_iter().next().unwrap()
543 } else {
544 Min(flatten)
545 }
546 }
547 Max(terms) => {
548 let mut flatten: Vec<TDim> = terms
549 .into_iter()
550 .map(|t| t.simplify_rec(scope, scenario))
551 .flat_map(|t| if let Max(t) = t { t } else { vec![t] })
552 .sorted_by(tdim_lexi_order)
553 .dedup()
554 .collect();
555 #[allow(clippy::mutable_key_type)]
556 let mut redundant = HashSet::<TDim>::default();
557 for pair in flatten.iter().permutations(2) {
558 let (a, b) = (pair[0], pair[1]);
559 if redundant.contains(a) || redundant.contains(b) {
560 continue;
561 }
562 let diff = a.clone() - b;
563 if diff.as_i64().is_some_and(|i| i >= 0) || scope.prove_positive_or_zero(&diff)
564 {
565 redundant.insert(b.clone());
566 }
567 }
568 flatten.retain(|t| !redundant.contains(t));
569 if flatten.len() == 0 {
570 i64::MIN.to_dim()
571 } else if flatten.len() == 1 {
572 flatten.into_iter().next().unwrap()
573 } else {
574 Max(flatten)
575 }
576 }
577 Sym(s) => scope
578 .assertions(scenario)
579 .find_map(|a| match a {
580 Assertion::Eq(Sym(sym), v) if sym == &s => Some(v.clone()),
581 _ => None,
582 })
583 .unwrap_or(Sym(s)),
584 Val(_) => self,
585 }
586 }
587
588 pub(super) fn inclusive_bound(&self, scope: &SymbolScopeData, upper: bool) -> Option<i64> {
589 use self::TDim::*;
590 match self {
591 Val(n) => Some(*n),
592 Sym(_) => {
593 if upper {
594 scope
595 .all_assertions()
596 .iter()
597 .filter_map(|assert| match &assert {
598 Assertion::LT(left, right)
599 if left == self && right.as_i64().is_some() =>
600 {
601 Some(right.as_i64().unwrap() - 1)
602 }
603 Assertion::LTE(left, right)
604 if left == self && right.as_i64().is_some() =>
605 {
606 Some(right.as_i64().unwrap())
607 }
608 _ => None,
609 })
610 .min()
611 } else {
612 scope
613 .all_assertions()
614 .iter()
615 .filter_map(|assert| match &assert {
616 Assertion::GT(left, right)
617 if left == self && right.as_i64().is_some() =>
618 {
619 Some(right.as_i64().unwrap() + 1)
620 }
621 Assertion::GTE(left, right)
622 if left == self && right.as_i64().is_some() =>
623 {
624 Some(right.as_i64().unwrap())
625 }
626 _ => None,
627 })
628 .max()
629 }
630 }
631 Add(terms) => {
632 let mut bound = 0;
633 for t in terms {
634 if let Some(b) = t.inclusive_bound(scope, upper) {
635 bound += b;
636 } else {
637 return None;
638 }
639 }
640 Some(bound)
641 }
642 MulInt(p, a) => match p.cmp(&0) {
643 Ordering::Equal => Some(0),
644 Ordering::Greater => a.inclusive_bound(scope, upper).map(|x| x * p),
645 Ordering::Less => a.inclusive_bound(scope, !upper).map(|x| x * p),
646 },
647 Mul(_) => None,
648 Min(terms) if !upper => {
649 terms.iter().filter_map(|t| t.inclusive_bound(scope, false)).min()
650 }
651 Max(terms) if upper => {
652 terms.iter().filter_map(|t| t.inclusive_bound(scope, true)).max()
653 }
654 Div(a, q) => a.inclusive_bound(scope, upper).map(|x| x / (*q as i64)),
655 Broadcast(terms) => {
656 if upper {
657 Max(terms.clone()).inclusive_bound(scope, true)
658 } else {
659 Min(terms.clone()).inclusive_bound(scope, false)
660 }
661 }
662 _ => None,
663 }
664 }
665
666 pub fn low_inclusive_bound(&self) -> Option<i64> {
667 if let TDim::Val(v) = self {
668 return Some(*v);
669 }
670 let scope = self.find_scope()?;
671 let data = scope.0.lock();
672 let data = data.borrow();
673 self.inclusive_bound(&data, false)
674 }
675
676 pub fn high_inclusive_bound(&self) -> Option<i64> {
677 if let TDim::Val(v) = self {
678 return Some(*v);
679 }
680 let scope = self.find_scope()?;
681 let data = scope.0.lock();
682 let data = data.borrow();
683 self.inclusive_bound(&data, true)
684 }
685
686 pub fn prove_positive_or_zero(&self) -> bool {
687 if let TDim::Val(v) = self {
688 return *v >= 0;
689 }
690 let Some(scope) = self.find_scope() else { return false };
691 let data = scope.0.lock();
692 let data = data.borrow();
693 data.prove_positive_or_zero(self)
694 }
695
696 pub fn prove_strict_positive(&self) -> bool {
697 if let TDim::Val(v) = self {
698 return *v > 0;
699 }
700 (self.clone() - 1).prove_positive_or_zero()
701 }
702
703 pub fn prove_negative_or_zero(&self) -> bool {
704 if let TDim::Val(v) = self {
705 return *v <= 0;
706 }
707 self.clone().neg().prove_positive_or_zero()
708 }
709
710 pub fn prove_strict_negative(&self) -> bool {
711 if let TDim::Val(v) = self {
712 return *v < 0;
713 }
714 self.clone().neg().prove_strict_positive()
715 }
716
717 pub fn gcd(&self) -> u64 {
718 use self::TDim::*;
719 match self {
720 Val(v) => v.unsigned_abs(),
721 Sym(_) => 1,
722 Add(terms) => {
723 let (head, tail) = terms.split_first().unwrap();
724 tail.iter().fold(head.gcd(), |a, b| a.gcd(&b.gcd()))
725 }
726 MulInt(p, a) => a.gcd() * p.unsigned_abs(),
727 Mul(terms) => terms.iter().map(|t| t.gcd()).product(),
728 Min(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),
729 Max(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),
730 Div(a, q) => {
731 if a.gcd() % *q == 0 {
732 a.gcd() / *q
733 } else {
734 1
735 }
736 }
737 Broadcast(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap_or(1),
738 }
739 }
740
741 fn div(&self, d: u64) -> TDim {
742 use self::TDim::*;
743 if d == 1 {
744 return self.clone();
745 }
746 match self {
747 Val(v) => Val(v / d as i64),
748 Sym(_) => panic!(),
749 Add(terms) => Add(terms.iter().map(|t| t.div(d)).collect()),
750 Min(terms) => Min(terms.iter().map(|t| t.div(d)).collect()),
751 Max(terms) => Max(terms.iter().map(|t| t.div(d)).collect()),
752 Broadcast(terms) => Broadcast(terms.iter().map(|t| t.div(d)).collect()),
753 Mul(_) => Div(Box::new(self.clone()), d),
754 MulInt(p, a) => {
755 if *p == d as i64 {
756 (**a).clone()
757 } else {
758 let gcd = p.unsigned_abs().gcd(&d);
759 MulInt(p / gcd as i64, b!(a.div(d / gcd)))
760 }
761 }
762 Div(a, q) => Div(a.clone(), q * d),
763 }
764 }
765
766 pub fn div_ceil(self, rhs: u64) -> TDim {
767 TDim::Div(Box::new(Add(vec![self, Val(rhs as i64 - 1)])), rhs).reduce()
768 }
769
770 pub(super) fn guess_slope(&self, sym: &Symbol) -> (i64, u64) {
771 fn slope_rec(d: &TDim, sym: &Symbol) -> (i64, i64) {
772 match d {
773 Val(_) => (0, 1),
774 Sym(s) => ((sym == s) as i64, 1),
775 Add(terms) => terms
776 .iter()
777 .map(|d| slope_rec(d, sym))
778 .fold((0, 1), |a, b| ((a.0 * b.1 + a.1 * b.0), (b.1 * a.1))),
779 Mul(terms) => terms
780 .iter()
781 .map(|d| slope_rec(d, sym))
782 .fold((1, 1), |a, b| ((a.0 * b.0), (b.1 * a.1))),
783 MulInt(p, a) => {
784 let (n, d) = slope_rec(a, sym);
785 (p * n, d)
786 }
787 Div(a, q) => {
788 let (n, d) = slope_rec(a, sym);
789 (n, d * *q as i64)
790 }
791 Broadcast(terms) => slope_rec(&terms[0], sym),
792 Min(terms) => slope_rec(&terms[0], sym),
793 Max(terms) => slope_rec(&terms[0], sym),
794 }
795 }
796 let (p, q) = slope_rec(self, sym);
797 reduce_ratio(p, q)
798 }
799
800 #[allow(clippy::mutable_key_type)]
801 pub fn symbols(&self) -> std::collections::HashSet<Symbol> {
802 match self {
803 Val(_) => maplit::hashset!(),
804 Sym(s) => maplit::hashset!(s.clone()),
805 Add(terms) | Mul(terms) | Broadcast(terms) | Min(terms) | Max(terms) => {
806 terms.iter().fold(maplit::hashset!(), |mut set, v| {
807 set.extend(v.symbols());
808 set
809 })
810 }
811 MulInt(_, a) => a.symbols(),
812 Div(a, _) => a.symbols(),
813 }
814 }
815
816 pub fn compatible_with(&self, other: &TDim) -> bool {
817 if let Ok(x) = (self.clone() - other).to_i64() {
818 return x == 0;
819 }
820 true }
822}
823
824pub(super) fn reduce_ratio(mut p: i64, mut q: i64) -> (i64, u64) {
825 let gcd = p.abs().gcd(&q.abs());
826 if gcd > 1 {
827 p /= gcd;
828 q /= gcd;
829 }
830 if q < 0 {
831 (-p, (-q) as u64)
832 } else {
833 (p, q as u64)
834 }
835}
836
837impl Zero for TDim {
838 fn zero() -> Self {
839 Val(0)
840 }
841 fn is_zero(&self) -> bool {
842 matches!(self, Val(0))
843 }
844}
845
846impl Default for TDim {
847 fn default() -> TDim {
848 Val(0)
849 }
850}
851
852impl num_traits::Bounded for TDim {
853 fn min_value() -> Self {
854 TDim::Val(i64::MIN)
855 }
856
857 fn max_value() -> Self {
858 TDim::Val(i64::MAX)
859 }
860}
861
862impl num_traits::One for TDim {
863 fn one() -> Self {
864 TDim::Val(1)
865 }
866}
867
868impl ::std::iter::Sum for TDim {
869 fn sum<I: Iterator<Item = TDim>>(iter: I) -> TDim {
870 iter.fold(0.into(), |a, b| a + b)
871 }
872}
873
874impl<'a> ::std::iter::Sum<&'a TDim> for TDim {
875 fn sum<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {
876 iter.fold(0.into(), |a, b| a + b)
877 }
878}
879
880impl std::iter::Product for TDim {
881 fn product<I: Iterator<Item = TDim>>(iter: I) -> Self {
882 iter.fold(TDim::Val(1), |a, b| a * b)
883 }
884}
885
886impl<'a> ::std::iter::Product<&'a TDim> for TDim {
887 fn product<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {
888 iter.fold(1.into(), |a, b| a * b)
889 }
890}
891
892macro_rules! from_i {
893 ($i: ty) => {
894 impl From<$i> for TDim {
895 fn from(v: $i) -> TDim {
896 TDim::Val(v as _)
897 }
898 }
899 impl<'a> From<&'a $i> for TDim {
900 fn from(v: &'a $i) -> TDim {
901 TDim::Val(*v as _)
902 }
903 }
904 };
905}
906
907from_i!(i32);
908from_i!(i64);
909from_i!(u64);
910from_i!(isize);
911from_i!(usize);
912
913impl From<Symbol> for TDim {
914 fn from(it: Symbol) -> Self {
915 TDim::Sym(it)
916 }
917}
918
919impl<'a> From<&'a Symbol> for TDim {
920 fn from(it: &'a Symbol) -> Self {
921 TDim::Sym(it.clone())
922 }
923}
924
925impl ops::Neg for TDim {
926 type Output = Self;
927 fn neg(self) -> Self {
928 if let Val(v) = self {
929 Val(-v)
930 } else {
931 TDim::MulInt(-1, Box::new(self)).reduce()
932 }
933 }
934}
935
936impl<'a> ops::AddAssign<&'a TDim> for TDim {
937 fn add_assign(&mut self, rhs: &'a TDim) {
938 if rhs.is_zero() {
939 } else if self.is_zero() {
940 *self = rhs.clone();
941 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
942 *s += o;
943 } else {
944 *self = TDim::Add(vec![std::mem::take(self), rhs.clone()]).reduce()
945 }
946 }
947}
948
949impl<I> ops::AddAssign<I> for TDim
950where
951 I: Into<TDim>,
952{
953 fn add_assign(&mut self, rhs: I) {
954 let rhs = rhs.into();
955 if rhs.is_zero() {
956 } else if self.is_zero() {
957 *self = rhs;
958 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
959 *s += o;
960 } else {
961 *self = TDim::Add(vec![std::mem::take(self), rhs]).reduce()
962 }
963 }
964}
965
966impl<I> ops::Add<I> for TDim
967where
968 I: Into<TDim>,
969{
970 type Output = Self;
971 fn add(mut self, rhs: I) -> Self {
972 self += rhs;
973 self
974 }
975}
976
977impl<'a> ops::Add<&'a TDim> for TDim {
978 type Output = Self;
979 fn add(mut self, rhs: &'a TDim) -> Self {
980 self += rhs;
981 self
982 }
983}
984
985#[allow(clippy::suspicious_op_assign_impl)]
986impl<'a> ops::SubAssign<&'a TDim> for TDim {
987 fn sub_assign(&mut self, rhs: &'a TDim) {
988 if rhs.is_zero() {
989 } else if self.is_zero() {
990 *self = rhs.clone().neg();
991 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
992 *s -= o;
993 } else {
994 *self = TDim::Add(vec![std::mem::take(self), rhs.clone().neg()]).reduce()
995 }
996 }
997}
998
999impl<I> ops::SubAssign<I> for TDim
1000where
1001 I: Into<TDim>,
1002{
1003 fn sub_assign(&mut self, rhs: I) {
1004 let rhs = rhs.into();
1005 if rhs.is_zero() {
1006 } else if self.is_zero() {
1007 *self = rhs.neg();
1008 } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {
1009 *s -= o;
1010 } else {
1011 *self = TDim::Add(vec![std::mem::take(self), rhs.neg()]).reduce()
1012 }
1013 }
1014}
1015
1016impl<I> ops::Sub<I> for TDim
1017where
1018 I: Into<TDim>,
1019{
1020 type Output = Self;
1021 fn sub(mut self, rhs: I) -> Self {
1022 self -= rhs;
1023 self
1024 }
1025}
1026
1027impl<'a> ops::Sub<&'a TDim> for TDim {
1028 type Output = Self;
1029 fn sub(mut self, rhs: &'a TDim) -> Self {
1030 self -= rhs;
1031 self
1032 }
1033}
1034
1035impl<I: Into<TDim>> ops::MulAssign<I> for TDim {
1036 fn mul_assign(&mut self, rhs: I) {
1037 let rhs = rhs.into();
1038 if self.is_one() {
1039 *self = rhs
1040 } else if rhs.is_one() {
1041 } else {
1042 *self = TDim::Mul(vec![rhs, std::mem::take(self)]).reduce()
1043 }
1044 }
1045}
1046
1047impl<'a> ops::MulAssign<&'a TDim> for TDim {
1048 fn mul_assign(&mut self, rhs: &'a TDim) {
1049 if self.is_one() {
1050 *self = rhs.clone()
1051 } else if rhs.is_one() {
1052 } else {
1053 *self = TDim::Mul(vec![std::mem::take(self), rhs.clone()]).reduce()
1054 }
1055 }
1056}
1057
1058impl<I: Into<TDim>> ops::Mul<I> for TDim {
1059 type Output = Self;
1060 fn mul(mut self, rhs: I) -> Self {
1061 self *= rhs.into();
1062 self
1063 }
1064}
1065
1066impl<'a> ops::Mul<&'a TDim> for TDim {
1067 type Output = Self;
1068 fn mul(mut self, rhs: &'a TDim) -> Self {
1069 self *= rhs;
1070 self
1071 }
1072}
1073
1074impl<I: AsPrimitive<u64> + PrimInt> ops::DivAssign<I> for TDim {
1075 fn div_assign(&mut self, rhs: I) {
1076 *self = TDim::Div(Box::new(std::mem::take(self)), rhs.as_()).reduce()
1077 }
1078}
1079
1080impl<I: AsPrimitive<u64> + PrimInt> ops::Div<I> for TDim {
1081 type Output = Self;
1082 fn div(mut self, rhs: I) -> Self {
1083 self /= rhs.as_();
1084 self
1085 }
1086}
1087
1088impl<I: AsPrimitive<u64> + PrimInt> ops::RemAssign<I> for TDim {
1089 fn rem_assign(&mut self, rhs: I) {
1090 *self += -(self.clone() / rhs.as_() * rhs.as_());
1091 }
1092}
1093
1094impl<I: AsPrimitive<u64> + PrimInt> ops::Rem<I> for TDim {
1095 type Output = Self;
1096 fn rem(mut self, rhs: I) -> Self {
1097 self %= rhs;
1098 self
1099 }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use super::*;
1105
1106 macro_rules! b( ($e:expr) => { Box::new($e) } );
1107
1108 lazy_static::lazy_static! {
1109 static ref table: SymbolScope = SymbolScope::default();
1110 static ref A: Symbol = table.sym("a");
1111 static ref B: Symbol = table.sym("b");
1112 }
1113
1114 fn neg(a: &TDim) -> TDim {
1115 mul(-1, a)
1116 }
1117
1118 fn add(a: &TDim, b: &TDim) -> TDim {
1119 TDim::Add(vec![a.clone(), b.clone()])
1120 }
1121
1122 fn mul(a: i64, b: &TDim) -> TDim {
1123 TDim::MulInt(a, b![b.clone()])
1124 }
1125
1126 fn div(a: &TDim, b: u64) -> TDim {
1127 TDim::Div(b!(a.clone()), b)
1128 }
1129
1130 #[test]
1131 fn reduce_add() {
1132 assert_eq!(add(&A.to_dim(), &neg(&A.to_dim())).reduce(), Val(0))
1133 }
1134
1135 #[test]
1136 fn reduce_neg_mul() {
1137 assert_eq!(neg(&mul(2, &A.to_dim())).reduce(), mul(-2, &A.to_dim()))
1138 }
1139
1140 #[test]
1141 fn reduce_cplx_ex_2() {
1142 assert_eq!(
1143 add(
1144 &add(&Val(-4), &mul(-2, &div(&A.to_dim(), 4))),
1145 &mul(-2, &mul(-1, &div(&A.to_dim(), 4)))
1146 )
1147 .reduce(),
1148 Val(-4)
1149 )
1150 }
1151
1152 #[test]
1153 fn reduce_cplx_ex_3() {
1154 assert_eq!(div(&MulInt(1, b!(MulInt(4, b!(A.to_dim())))), 4).reduce(), A.to_dim())
1155 }
1156
1157 #[test]
1158 fn reduce_cplx_ex_4() {
1159 assert_eq!(
1161 add(&div(&add(&A.to_dim(), &Val(1)), 2), &div(&add(&neg(&A.to_dim()), &Val(1)), 2))
1162 .reduce(),
1163 1.into()
1164 );
1165 }
1166
1167 #[test]
1168 fn reduce_mul_mul_1() {
1169 assert_eq!(mul(3, &mul(2, &A.to_dim())).reduce(), mul(6, &A.to_dim()))
1170 }
1171
1172 #[test]
1173 fn reduce_mul_mul_2() {
1174 assert_eq!(mul(-2, &mul(-1, &A.to_dim())).reduce(), mul(2, &A.to_dim()))
1175 }
1176
1177 #[test]
1178 fn reduce_mul_div_1() {
1179 assert_eq!(mul(2, &div(&mul(-1, &A.to_dim()), 3)).reduce(), mul(-2, &div(&A.to_dim(), 3)))
1180 }
1181
1182 #[test]
1183 fn const_and_add() {
1184 let e: TDim = 2i64.into();
1185 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 2);
1186 let e: TDim = TDim::from(2) + 3;
1187 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 5);
1188 let e: TDim = TDim::from(2) - 3;
1189 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -1);
1190 let e: TDim = -TDim::from(2);
1191 assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -2);
1192 }
1193
1194 #[test]
1195 fn substitution() {
1196 let a: TDim = A.to_dim();
1197 assert_eq!(a.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 2);
1198 let e = a + 3;
1199 assert_eq!(e.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 5);
1200 }
1201
1202 #[test]
1203 fn reduce_adds() {
1204 let e: TDim = TDim::from(2) + 1;
1205 assert_eq!(e, TDim::from(3));
1206 let e: TDim = TDim::from(3) + 2;
1207 assert_eq!(e, TDim::from(5));
1208 let e: TDim = TDim::from(3) + 0;
1209 assert_eq!(e, TDim::from(3));
1210 let e: TDim = TDim::from(3) + 2 + 1;
1211 assert_eq!(e, TDim::from(6));
1212 }
1213
1214 #[test]
1215 fn reduce_muls() {
1216 let e: TDim = Val(1) * A.to_dim();
1217 assert_eq!(e, A.to_dim());
1218 let e: TDim = A.to_dim() * &B.to_dim() * 1;
1219 assert_eq!(e, A.to_dim() * &B.to_dim());
1220 }
1221
1222 #[test]
1223 fn reduce_divs() {
1224 let e: TDim = TDim::from(2) / 1;
1225 assert_eq!(e, TDim::from(2));
1226 let e: TDim = TDim::from(3) / 2;
1227 assert_eq!(e, TDim::from(1));
1228 let e: TDim = TDim::from(3) % 2;
1229 assert_eq!(e, TDim::from(1));
1230 let e: TDim = TDim::from(5) / 2;
1231 assert_eq!(e, TDim::from(2));
1232 let e: TDim = TDim::from(5) % 2;
1233 assert_eq!(e, TDim::from(1));
1234 }
1235
1236 #[test]
1237 fn reduce_div_bug_0() {
1238 let e1: TDim = (A.to_dim() + 23) / 2 - 1;
1239 let e2: TDim = (A.to_dim() + 21) / 2;
1240 assert_eq!(e1, e2);
1241 }
1242
1243 #[test]
1244 fn reduce_div_bug_1() {
1245 let e1: TDim = (A.to_dim() + -1) / 2;
1246 let e2: TDim = (A.to_dim() + 1) / 2 - 1;
1247 assert_eq!(e1, e2);
1248 }
1249
1250 #[test]
1251 fn reduce_div_bug_2() {
1252 let e1: TDim = ((A.to_dim() + 1) / 2 + 1) / 2;
1253 let e2: TDim = (A.to_dim() + 3) / 4;
1254 assert_eq!(e1, e2);
1255 }
1256
1257 #[test]
1258 fn reduce_div_bug_3() {
1259 let e1: TDim = (A.to_dim() / 2) * -4;
1260 let e2: TDim = (A.to_dim() / 2) * -4 / 1;
1261 assert_eq!(e1, e2);
1262 }
1263
1264 #[test]
1265 fn reduce_mul_div() {
1266 let e: TDim = A.to_dim() * 2 / 2;
1267 assert_eq!(e, A.to_dim());
1268 }
1269
1270 #[test]
1271 fn reduce_div_mul() {
1272 let e: TDim = A.to_dim() / 2 * 2;
1273 assert_ne!(e, A.to_dim());
1274 }
1275
1276 #[test]
1277 fn reduce_add_div() {
1278 let e: TDim = A.to_dim() / 2 + 1;
1279 assert_eq!(e, ((A.to_dim() + 2) / 2));
1280 }
1281
1282 #[test]
1283 fn reduce_neg_mul_() {
1284 let e: TDim = TDim::from(1) - A.to_dim() * 2;
1285 assert_eq!(e, TDim::from(1) + A.to_dim() * -2);
1286 }
1287
1288 #[test]
1289 fn reduce_add_rem_1() {
1290 assert_eq!(((A.to_dim() + 4) % 2), (A.to_dim() % 2));
1291 }
1292
1293 #[test]
1294 fn reduce_add_rem_2() {
1295 assert_eq!(((A.to_dim() - 4) % 2), (A.to_dim() % 2));
1296 }
1297
1298 #[test]
1299 fn reduce_rem_div() {
1300 let e: TDim = A.to_dim() % 2 / 2;
1301 assert_eq!(e, TDim::from(0));
1302 }
1303
1304 #[test]
1305 fn conv2d_ex_1() {
1306 let e = (TDim::from(1) - 1 + 1).div_ceil(1);
1307 assert_eq!(e, TDim::from(1));
1308 }
1309
1310 #[test]
1311 fn conv2d_ex_2() {
1312 let e = (A.to_dim() - 3 + 1).div_ceil(1);
1313 assert_eq!(e, A.to_dim() + -2);
1314 }
1315
1316 #[test]
1317 fn extract_int_gcd_from_muls() {
1318 let term = (A.to_dim() + 1) / 4;
1319 let mul = (term.clone() * 24 - 24) * (term.clone() * 2 - 2);
1320 let target = (term.clone() - 1) * (term.clone() - 1) * 48;
1321 assert_eq!(mul, target);
1322 }
1323
1324 #[test]
1325 fn equality_of_muls() {
1326 let term = (A.to_dim() + 1) / 4;
1327 let mul1 = (term.clone() * 2 - 3) * (term.clone() - 1);
1328 let mul2 = (term.clone() - 1) * (term.clone() * 2 - 3);
1329 assert_eq!(mul1, mul2);
1330 }
1331
1332 #[test]
1333 fn factorize_complex_expr_times_int() {
1334 let term = (A.to_dim() + 1) / 4;
1335 let e = term.clone() * 2 - &term - 1;
1336 assert_eq!(e, term - 1);
1337 }
1338
1339 #[test]
1340 fn min_ints_1() {
1341 assert_eq!(2.to_dim().mini(1.to_dim()), 1.to_dim());
1342 }
1343
1344 #[test]
1345 fn min_ints_2() {
1346 assert_eq!(1.to_dim().mini(2.to_dim()), 1.to_dim());
1347 }
1348
1349 #[test]
1350 fn min_same() {
1351 assert_eq!(A.to_dim().mini(A.to_dim()), A.to_dim());
1352 }
1353
1354 #[test]
1355 fn min_noop() {
1356 assert_eq!(A.to_dim().mini(1.to_dim()), A.to_dim().mini(1.to_dim()));
1357 }
1358
1359 #[test]
1360 fn min_diff_1() {
1361 assert_eq!((A.to_dim() + 1).mini(A.to_dim() + 2), A.to_dim() + 1);
1362 }
1363
1364 #[test]
1365 fn slope_0() {
1366 assert_eq!(12.to_dim().guess_slope(&A), (0, 1));
1367 }
1368
1369 #[test]
1370 fn slope_1() {
1371 assert_eq!(A.to_dim().guess_slope(&A), (1, 1));
1372 }
1373
1374 #[test]
1375 fn slope_2() {
1376 assert_eq!((A.to_dim() * 2).guess_slope(&A), (2, 1));
1377 }
1378
1379 #[test]
1380 fn slope_3() {
1381 assert_eq!((A.to_dim() * 2 + A.to_dim() / 2).guess_slope(&A), (5, 2));
1382 }
1383
1384 #[test]
1385 fn slope_4() {
1386 assert_eq!((A.to_dim()).guess_slope(&B), (0, 1));
1387 }
1388
1389 #[test]
1390 fn slope_5() {
1391 assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));
1392 assert_eq!((A.to_dim() + 1).guess_slope(&B), (0, 1));
1393 }
1394
1395 #[test]
1396 fn slope_6() {
1397 assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));
1398 assert_eq!((A.to_dim() + B.to_dim()).guess_slope(&B), (1, 1));
1399 }
1400
1401 #[test]
1402 fn min_0() -> TractResult<()> {
1403 let symbols = SymbolScope::default();
1404 assert_eq!(
1405 symbols.parse_tdim("min(S+3, S+2)").unwrap().simplify(),
1406 symbols.parse_tdim("S+2").unwrap(),
1407 );
1408 Ok(())
1409 }
1410}