1use crate::internal::*;
3use num_traits::{One, Zero};
4use std::fmt;
5use std::ops;
6
7mod assertion;
8mod parse;
9mod resolve;
10mod sym;
11mod tree;
12
13pub use self::assertion::Assertion;
14pub use self::parse::parse_tdim;
15pub use self::resolve::solve_for;
16pub use self::sym::{Symbol, SymbolScope, SymbolValues};
17pub use self::tree::{TDim, TooEarly};
18
19use crate::{TractError, TractResult};
20
21pub trait DimLike:
28 Clone
29 + Default
30 + PartialEq
31 + From<usize>
32 + for<'a> std::convert::TryFrom<&'a TDim, Error = TractError>
33 + ::num_traits::Zero
34 + fmt::Debug
35 + fmt::Display
36 + std::hash::Hash
37 + ops::Add<Self, Output = Self>
38 + ops::Add<usize, Output = Self>
39 + for<'a> ops::Add<&'a Self, Output = Self>
40 + ops::Sub<Self, Output = Self>
41 + ops::Sub<usize, Output = Self>
42 + for<'a> ops::Sub<&'a Self, Output = Self>
43 + ops::Mul<Self, Output = Self>
44 + ops::Mul<usize, Output = Self>
45 + for<'a> ops::Mul<&'a Self, Output = Self>
46 + ops::Div<usize, Output = Self>
47 + ops::Rem<usize, Output = Self>
48 + Send
49 + Sync
50 + 'static
51 + std::iter::Sum
52 + std::iter::Product
53 + ToDim
54 + One
55{
56 fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)>;
57
58 fn divceil(&self, other: usize) -> Self {
60 (self.clone() + other - 1) / other
61 }
62
63 fn to_i64(&self) -> Result<i64, TooEarly>;
68
69 fn as_i64(&self) -> Option<i64>;
72
73 fn to_usize(&self) -> Result<usize, TooEarly> {
74 self.to_i64().map(|d| d as usize)
75 }
76
77 fn as_usize(&self) -> Option<usize> {
78 self.as_i64().map(|d| d as usize)
79 }
80
81 fn to_isize(&self) -> Result<isize, TooEarly> {
82 self.to_i64().map(|d| d as isize)
83 }
84
85 fn as_isize(&self) -> Option<isize> {
86 self.as_i64().map(|d| d as isize)
87 }
88
89 fn to_i32(&self) -> Result<i32, TooEarly> {
90 self.to_i64().map(|d| d as i32)
91 }
92
93 fn eval(&self, values: &SymbolValues) -> Self;
95
96 fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64>;
98
99 fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self>;
100 fn substitute_all(&self, map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self>;
101
102 fn broadcast(self, other: Self) -> TractResult<Self>;
103 fn mini(self, other: Self) -> Self;
104 fn maxi(self, other: Self) -> Self;
105
106 fn compatible_with(&self, other: &Self) -> bool;
107}
108
109impl DimLike for TDim {
110 fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)> {
111 if self.is_zero() {
112 return Ok((TDim::zero(), 1));
113 } else if other.is_zero() {
114 bail!("Division by zero")
115 }
116 if let TDim::Add(terms) = self
121 && terms.len() >= 2
122 && let Some(parts) =
123 terms.iter().map(|t| t.maybe_div(other).ok()).collect::<Option<Vec<_>>>()
124 && let Some((_, q0)) = parts.first()
125 && parts.iter().all(|(_, q)| q == q0)
126 {
127 let q = *q0;
128 let sum = parts.into_iter().map(|(d, _)| d).fold(TDim::zero(), |acc, d| acc + d);
129 return Ok((sum.reduce(), q));
130 }
131 fn expand(dim: &TDim) -> (i64, Vec<TDim>) {
132 match dim {
133 TDim::Mul(terms) => terms.iter().map(expand).fold((1i64, vec![]), |acc, t| {
134 (acc.0 * t.0, acc.1.into_iter().chain(t.1).collect())
135 }),
136 TDim::MulInt(a, terms) => {
137 let (b, v) = expand(terms);
138 (a * b, v)
139 }
140 TDim::Val(x) => (*x, vec![]),
141 TDim::Add(terms) => {
142 let gcd =
143 terms.iter().map(expand).map(|(n, _)| n).reduce(|a, b| a.gcd(&b)).unwrap();
144 (
145 gcd,
146 vec![TDim::Add(terms.iter().map(|t| t.clone() / gcd).collect()).simplify()],
147 )
148 }
149 it => (1, vec![it.clone()]),
150 }
151 }
152 let (mut num_int, mut num) = expand(self);
153 let (mut denum_int, mut denum) = expand(other);
154 if num == denum {
155 num = vec![];
156 denum = vec![];
157 }
158 for it in denum {
159 if let Some(pos) = num.iter().position(|n| n == &it) {
160 num.remove(pos);
161 } else {
162 bail!("Can't divide {} by {}", self, other)
163 }
164 }
165 use num_integer::Integer;
166 if denum_int < 0 {
167 num_int *= -1;
168 denum_int *= -1;
169 }
170 let gcd = num_int.gcd(&denum_int);
171 num_int /= gcd;
172 denum_int /= gcd;
173 Ok(((TDim::Mul(num) * num_int).reduce(), denum_int as u64))
174 }
175
176 fn to_i64(&self) -> Result<i64, TooEarly> {
177 TDim::to_i64(self)
178 }
179
180 fn as_i64(&self) -> Option<i64> {
181 TDim::as_i64(self)
182 }
183
184 fn eval(&self, values: &SymbolValues) -> Self {
185 self.eval(values)
186 }
187
188 fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self> {
189 self.substitute(from, to)
190 }
191
192 fn substitute_all(&self, map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self> {
193 TDim::substitute_all(self, map)
194 }
195
196 fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64> {
197 TDim::eval_to_i64(self, values)
198 }
199
200 fn broadcast(self, other: Self) -> TractResult<Self> {
201 if self.is_one() {
202 Ok(other)
203 } else if other.is_one() {
204 Ok(self)
205 } else {
206 Ok(TDim::Broadcast(vec![self, other]).simplify())
207 }
208 }
209
210 fn compatible_with(&self, other: &Self) -> bool {
211 self.compatible_with(other)
212 }
213
214 fn mini(self, other: Self) -> Self {
215 TDim::Min(vec![self, other]).simplify()
216 }
217
218 fn maxi(self, other: Self) -> Self {
219 TDim::Max(vec![self, other]).simplify()
220 }
221}
222
223impl<'a> std::convert::TryFrom<&'a TDim> for TDim {
224 type Error = TractError;
225 fn try_from(d: &'a TDim) -> TractResult<TDim> {
226 Ok(d.clone())
227 }
228}
229
230impl DimLike for usize {
231 fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)> {
232 use num_integer::Integer;
233 let gcd = self.gcd(other);
234 Ok((self / gcd, (other / gcd) as u64))
235 }
236
237 fn to_i64(&self) -> Result<i64, TooEarly> {
238 Ok(*self as i64)
239 }
240
241 fn as_i64(&self) -> Option<i64> {
242 Some(*self as i64)
243 }
244
245 fn eval(&self, _values: &SymbolValues) -> Self {
246 *self
247 }
248
249 fn substitute(&self, _from: &Symbol, _to: &Self) -> TractResult<Self> {
250 Ok(*self)
251 }
252
253 fn substitute_all(&self, _map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self> {
254 Ok(*self)
255 }
256
257 fn eval_to_i64(&self, _: &SymbolValues) -> TractResult<i64> {
258 Ok(*self as i64)
259 }
260
261 fn broadcast(self, other: Self) -> TractResult<Self> {
262 if self == 1 || self == other {
263 Ok(other)
264 } else if other == 1 {
265 Ok(self)
266 } else {
267 bail!("Can not broadcast {self} against {other}")
268 }
269 }
270
271 fn compatible_with(&self, other: &Self) -> bool {
272 self == other
273 }
274
275 fn mini(self, other: Self) -> Self {
276 if self < other { self } else { other }
277 }
278
279 fn maxi(self, other: Self) -> Self {
280 if self > other { self } else { other }
281 }
282}
283
284impl<'a> std::convert::TryFrom<&'a TDim> for usize {
285 type Error = TractError;
286 fn try_from(d: &'a TDim) -> TractResult<usize> {
287 Ok(d.to_usize()?)
288 }
289}
290
291pub trait ToDim {
293 fn to_dim(&self) -> TDim;
295}
296
297impl<I: Into<TDim> + Clone> ToDim for I {
298 fn to_dim(&self) -> TDim {
299 self.clone().into()
300 }
301}
302
303impl ToDim for &TDim {
304 fn to_dim(&self) -> TDim {
305 (*self).clone()
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 lazy_static::lazy_static! {
314 static ref S: (SymbolScope, Symbol) = {
315 let table = SymbolScope::default();
316 let s = table.new_with_prefix("S");
317 (table, s)
318 };
319 }
320
321 pub fn s() -> TDim {
322 S.1.clone().into()
323 }
324
325 #[test]
326 fn div() {
327 assert_eq!(TDim::from(12).maybe_div(&TDim::from(4)).unwrap(), (3.into(), 1));
328 }
329
330 #[test]
331 fn div_sym_int() {
332 assert_eq!((s() * 12).maybe_div(&TDim::from(4)).unwrap(), (s() * 3, 1));
333 }
334
335 #[test]
336 fn div_sym_sym() {
337 assert_eq!((s() * 12).maybe_div(&(s() * 4)).unwrap(), (3.into(), 1));
338 }
339
340 #[test]
341 fn div_sym_sym_ratio() {
342 assert_eq!((s() * 13).maybe_div(&(s() * 4)).unwrap(), (13.into(), 4));
343 }
344
345 #[test]
346 fn div_sym_sym_rem() {
347 assert!((s() + 1).maybe_div(&(s() * 4)).is_err());
348 }
349
350 #[test]
351 fn div_sym_sym_simply_1() {
352 assert_eq!((s()).maybe_div(&(s())).unwrap(), (TDim::Val(1), 1));
353 }
354
355 #[test]
356 fn div_sym_sym_complex() {
357 let s = s();
358 let b = S.0.sym("b");
359 assert_eq!(
360 (256.to_dim() * &s * &b).maybe_div(&(1.to_dim() * &s * &b)).unwrap(),
361 (256.into(), 1)
362 );
363 }
364
365 #[test]
366 fn div_sym_sym_with_add() {
367 assert_eq!((s() * 80 - 160).maybe_div(&(s() - 2)).unwrap(), (80.into(), 1));
368 }
369
370 #[test]
371 fn div_with_shared_div_ceil_factor() {
372 let t: TDim = S.0.sym("T").into();
378 let slice = S.0.sym("slice");
379 let c = t.div_ceil(8); let num = 8.to_dim() * &slice * &c + 8.to_dim() * &c;
381 let denom = 8.to_dim() * &c;
382 assert_eq!(num.maybe_div(&denom).unwrap(), (slice.to_dim() + 1, 1));
383 }
384}