1use std::borrow::Borrow;
2use std::fmt::Debug;
3
4use crate::internal::*;
5use crate::model::{TypedModel, TypedNode};
6use crate::ops::identity::Identity;
7use AxisOp::*;
8use tract_itertools::Itertools;
9use tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};
10use tract_ndarray::{ArrayViewD, ArrayViewMutD};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum InOut {
14 Out(usize),
15 In(usize),
16}
17
18impl InOut {
19 pub fn as_outlet<F: Clone + Fact, O: Clone>(&self, node: &Node<F, O>) -> OutletId {
20 match self {
21 InOut::In(ix) => node.inputs[*ix],
22 InOut::Out(ix) => OutletId::new(node.id, *ix),
23 }
24 }
25
26 pub fn is_input(&self) -> bool {
27 matches!(self, InOut::In(_))
28 }
29
30 pub fn is_output(&self) -> bool {
31 matches!(self, InOut::Out(_))
32 }
33
34 pub fn slot(&self) -> usize {
35 match self {
36 InOut::Out(o) => *o,
37 InOut::In(i) => *i,
38 }
39 }
40}
41
42#[derive(Clone, Hash, Eq)]
43#[allow(clippy::large_enum_variant)] #[allow(clippy::derived_hash_with_manual_eq)] pub enum AxisOp {
46 Add(usize),
47 Rm(usize),
48 Move(usize, usize),
49 Reshape(usize, TVec<TDim>, TVec<TDim>),
50}
51
52impl Debug for AxisOp {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 AxisOp::Add(a) => write!(f, "Add({a})"),
56 AxisOp::Rm(a) => write!(f, "Rm({a})"),
57 AxisOp::Move(from, to) => write!(f, "Move({from},{to})"),
58 AxisOp::Reshape(at, from, to) => {
59 write!(f, "Reshape({at}, [{}], [{}])", from.iter().join(","), to.iter().join(","))
60 }
61 }
62 }
63}
64
65impl PartialEq for AxisOp {
66 fn eq(&self, other: &AxisOp) -> bool {
67 if self.is_noop() && other.is_noop() {
68 true
69 } else if self.is_noop() != other.is_noop() {
70 false
71 } else {
72 match (self, other) {
73 (Add(a), Add(b)) | (Rm(a), Rm(b)) => a == b,
74 (Move(f1, t1), Move(f2, t2)) => {
75 (f1 == f2 && t1 == t2)
76 || ((*t1 == f1 + 1 || *f1 == t1 + 1) && t2 == f1 && t1 == f2)
77 }
78 (Reshape(at1, f1, t1), Reshape(at2, f2, t2)) => at1 == at2 && f1 == f2 && t1 == t2,
79 _ => false,
80 }
81 }
82 }
83}
84
85impl AxisOp {
86 pub fn canonical(&self) -> Cow<'_, AxisOp> {
87 match self {
88 Move(from, to) if *from == to + 1 => Cow::Owned(Move(*to, *from)),
89 Reshape(at, from, to)
90 if from.len() == 1 && to.len() == 2 && from[0] == to[0] && to[1].is_one() =>
91 {
92 Cow::Owned(Add(*at + 1))
93 }
94 Reshape(at, from, to)
95 if from.len() == 1 && to.len() == 2 && from[0] == to[1] && to[0].is_one() =>
96 {
97 Cow::Owned(Add(*at))
98 }
99 Reshape(at, from, to)
100 if from.len() == 2 && to.len() == 1 && from[0] == to[0] && from[1].is_one() =>
101 {
102 Cow::Owned(Rm(*at + 1))
103 }
104 Reshape(at, from, to)
105 if from.len() == 2 && to.len() == 1 && from[1] == to[0] && from[0].is_one() =>
106 {
107 Cow::Owned(Rm(*at))
108 }
109 other => Cow::Borrowed(other),
110 }
111 }
112
113 pub fn simplify(&self) -> TVec<AxisOp> {
114 match self.canonical().borrow() {
115 Reshape(_, from, to) if from == to => tvec!(),
116 Reshape(at, from, to) if to.len() == 0 => tvec!(Rm(*at); from.len()),
117 Reshape(at, from, to) if from.len() == 0 => tvec!(Add(*at); to.len()),
118 Reshape(at, from, to) if from[0] == to[0] => {
119 Reshape(at + 1, from[1..].into(), to[1..].into()).simplify()
120 }
121 Reshape(at, from, to) if from[from.len() - 1] == to[to.len() - 1] => {
122 Reshape(*at, from[..from.len() - 1].into(), to[..to.len() - 1].into()).simplify()
123 }
124 Reshape(at, from, to) if from[0] == 1.to_dim() => std::iter::once(Rm(*at))
125 .chain(Reshape(*at, from[1..].into(), to.clone()).simplify())
126 .collect(),
127 Reshape(at, from, to) if to[0] == 1.to_dim() => {
128 Reshape(*at, from.clone(), to[1..].into())
129 .simplify()
130 .into_iter()
131 .chain(std::iter::once(Add(*at)))
132 .collect()
133 }
134 Reshape(at, from, to) if from[from.len() - 1] == 1.to_dim() => {
135 std::iter::once(Rm(at + from.len() - 1))
136 .chain(Reshape(*at, from[..from.len() - 1].into(), to.clone()).simplify())
137 .collect()
138 }
139 Reshape(at, from, to) if to[to.len() - 1] == 1.to_dim() => {
140 std::iter::once(Add(at + from.len()))
141 .chain(Reshape(*at, from.clone(), to[..to.len() - 1].into()).simplify())
142 .collect()
143 }
144 other => tvec!(other.clone()),
145 }
146 }
147
148 pub fn transform_axis(&self, axis: usize) -> Option<usize> {
149 match self.canonical().as_ref() {
150 Add(ix) => Some(axis + (axis >= *ix) as usize),
151 Rm(ix) => {
152 if axis == *ix {
153 None
154 } else {
155 Some(axis - (axis > *ix) as usize)
156 }
157 }
158 Move(from, to) if from < to => {
159 if axis < *from || axis > *to {
160 Some(axis)
161 } else if axis == *from {
162 Some(*to)
163 } else {
164 Some(axis - 1)
165 }
166 }
167 Move(from, to) => {
168 if axis < *to || axis > *from {
169 Some(axis)
170 } else if axis == *from {
171 Some(*to)
172 } else {
173 Some(axis + 1)
174 }
175 }
176 Reshape(at, _, _) if axis < *at => Some(axis),
177 Reshape(at, from, to) if axis >= at + from.len() => Some(axis + to.len() - from.len()),
178 Reshape(_, _, _) => None,
179 }
180 }
181
182 pub fn merge_incoming_change(
187 &self,
188 change: &AxisOp,
189 ) -> Option<(Option<AxisOp>, Option<AxisOp>)> {
190 match (self.canonical().as_ref(), change.canonical().as_ref()) {
191 (Add(op), Add(c)) => {
192 Some((Some(Add(op + (c < op) as usize)), Some(Add(c + (c >= op) as usize))))
193 }
194 (Add(op), Rm(c)) => {
195 Some((Some(Add(op - (c < op) as usize)), Some(Rm(c + (c >= op) as usize))))
196 }
197 (Rm(op), Add(c)) => {
198 Some((Some(Rm(op + (c <= op) as usize)), Some(Add(c - (op < c) as usize))))
199 }
200 (Rm(op), Rm(c)) => {
201 Some((Some(Rm(op - (c < op) as usize)), Some(Rm(c - (op <= c) as usize))))
202 }
203
204 (Add(x), Move(from, to)) => {
205 if x <= from.min(to) {
206 Some((Some(self.clone()), Some(Move(from + 1, to + 1))))
207 } else if x > from.max(to) {
208 Some((Some(self.clone()), Some(change.clone())))
209 } else {
210 None
211 }
212 }
213
214 (Move(from, to), Add(x)) => {
215 if x <= from.min(to) {
216 Some((Some(Move(from + 1, to + 1)), Some(Add(*x))))
217 } else if x > from.max(to) {
218 Some((Some(Move(*from, *to)), Some(Add(*x))))
219 } else {
220 None
221 }
222 }
223
224 (Rm(x), Move(from, to)) => {
225 if x == from {
226 Some((Some(Rm(*to)), None))
227 } else if x < from.min(to) {
228 Some((Some(self.clone()), Some(Move(from - 1, to - 1))))
229 } else if x > from.max(to) {
230 Some((Some(self.clone()), Some(change.clone())))
231 } else if from + 1 == *to && x == to {
232 Some((Some(Rm(*from)), None))
233 } else if from < to && x <= to {
234 Some((Some(Rm(x - 1)), Some(Move(*from, *to - 1))))
235 } else {
236 Some((Some(Rm(x + 1)), Some(Move(*from - 1, *to))))
237 }
238 }
239
240 (Move(from, to), Rm(x)) => {
241 if x < from.min(to) {
242 Some((Some(Move(from - 1, to - 1)), Some(Rm(*x))))
243 } else if x > from.max(to) {
244 Some((Some(Move(*from, *to)), Some(Rm(*x))))
245 } else {
246 None
247 }
248 }
249
250 (Add(op), Reshape(at, from, to)) => {
251 if op <= at {
252 Some((Some(Add(*op)), Some(Reshape(at + 1, from.clone(), to.clone()))))
253 } else if *op > at + from.len() {
254 Some((
255 Some(Add(*op + to.len() - from.len())),
256 Some(Reshape(*at, from.clone(), to.clone())),
257 ))
258 } else {
259 None
260 }
261 }
262 (Rm(op), Reshape(at, from, to)) => {
263 if op < at {
264 Some((Some(Rm(*op)), Some(Reshape(at - 1, from.clone(), to.clone()))))
265 } else if *op > at + from.len() {
266 Some((
267 Some(Rm(*op + to.len() - from.len())),
268 Some(Reshape(*at, from.clone(), to.clone())),
269 ))
270 } else {
271 None
272 }
273 }
274 (Reshape(at, from, to), Add(change)) => {
275 if change < at {
276 Some((Some(Reshape(at + 1, from.clone(), to.clone())), Some(Add(*change))))
277 } else if *change > *at + from.len() {
278 Some((
279 Some(Reshape(*at, from.clone(), to.clone())),
280 Some(Add(change + to.len() - from.len())),
281 ))
282 } else {
283 None
284 }
285 }
286 (Reshape(at, from, to), Rm(change)) => {
287 if change < at {
288 Some((Some(Reshape(at - 1, from.clone(), to.clone())), Some(Rm(*change))))
289 } else if *change > *at + from.len() {
290 Some((
291 Some(Reshape(*at, from.clone(), to.clone())),
292 Some(Rm(change + to.len() - from.len())),
293 ))
294 } else {
295 None
296 }
297 }
298 (Reshape(_, _, _), Move(_, _)) => None, (Move(_, _), Reshape(_, _, _)) => None, (Reshape(_, _, _), Reshape(_, _, _)) => None, _ => None,
302 }
303 }
304
305 pub fn change_shape_array<D: DimLike>(
306 &self,
307 shape: &mut TVec<D>,
308 broadcasting: bool,
309 ) -> TractResult<()> {
310 match self.canonical().as_ref() {
311 Add(ix) => {
312 ensure!(*ix <= shape.len());
313 shape.insert(*ix, D::one());
314 }
315 Rm(ix) => {
316 ensure!(*ix < shape.len());
317 shape.remove(*ix);
318 }
319 Move(from, to) => {
320 ensure!(*from < shape.len());
321 ensure!(*to < shape.len());
322 let axis = shape.remove(*from);
323 shape.insert(*to, axis);
324 }
325 Reshape(at, from, to) => {
326 let from_volume = from.iter().product::<TDim>();
327 let to_volume = to.iter().product::<TDim>();
328 ensure!(
335 from_volume.clone().expand_polynomial()
336 == to_volume.clone().expand_polynomial(),
337 "{from_volume} should be equal to {to_volume}"
338 );
339 ensure!(*at + from.len() <= shape.len());
340 if shape.len() >= from.len() + *at
341 && tract_itertools::izip!(shape.iter().skip(*at), from)
342 .all(|(shape, spec)| shape.to_dim() == *spec)
343 {
344 for _ in from {
345 shape.remove(*at);
346 }
347 for d in to.iter().rev() {
348 shape.insert(*at, d.try_into()?);
349 }
350 } else if broadcasting
351 && shape.iter().skip(*at).take(from.len()).all(|d| d.to_dim() == 1.to_dim())
352 {
353 for _ in from {
354 shape.remove(*at);
355 }
356 for _ in to.iter().rev() {
357 shape.insert(*at, 1.into());
358 }
359 } else {
360 bail!("Incompatible reshape for shape {:?} and {:?}", shape, self);
361 }
362 }
363 }
364 Ok(())
365 }
366
367 pub fn change_shape(&self, shape: &mut ShapeFact, broadcasting: bool) -> TractResult<()> {
368 match self.canonical().as_ref() {
369 Add(ix) => {
370 if *ix > shape.rank() {
371 bail!("Attempt to insert axis #{} on shape {:?}", ix, shape);
372 }
373 shape.insert_axis(*ix)
374 }
375 Rm(ix) => {
376 if shape.rank() <= *ix {
377 bail!("Attempt to remove axis #{} on shape {:?}", ix, shape);
378 }
379 if shape[*ix] != 1.to_dim() {
380 bail!("Removing non-trivial axis #{} of dim: {:?}", ix, shape);
381 }
382 shape.remove_axis(*ix)
383 }
384 _ => {
385 let mut array = shape.to_tvec();
386 self.change_shape_array(&mut array, broadcasting)?;
387 let mut new_shape = ShapeFact::from_dims(array);
388 std::mem::swap(shape, &mut new_shape);
389 Ok(())
390 }
391 }
392 }
393
394 pub fn change_tensor(&self, tensor: &mut Tensor, broadcasting: bool) -> TractResult<()> {
395 if tensor.storage_as::<BlockQuantStorage>().is_some() {
396 let bqs = tensor.try_storage_as::<BlockQuantStorage>()?.clone();
397 let mut new_shape: TVec<usize> = tensor.shape().into();
398 self.change_shape_array(&mut new_shape, false)?;
399 let mut new_tensor = bqs.into_tensor_with_shape(tensor.datum_type(), &new_shape);
400 std::mem::swap(tensor, &mut new_tensor);
401 return Ok(());
402 }
403 ensure!(self.required_rank() <= tensor.rank());
404 match self.canonical().as_ref() {
405 Add(ix) => tensor.insert_axis(*ix),
406 Rm(ix) => tensor.remove_axis(*ix),
407 Move(from, to) => {
408 *tensor = tensor.moved_axis(*from, *to)?;
409 Ok(())
410 }
411 Reshape(at, from, to) => {
412 let mut shape: TVec<usize> = tensor.shape().into();
413 self.change_shape_array(&mut shape, true)?;
414 if tensor.set_shape(&shape).is_ok() {
415 Ok(())
416 } else if broadcasting
417 && tensor.shape().iter().skip(*at).take(from.len()).all(|d| *d == 1)
418 {
419 if from.len() > to.len() {
420 for _ in to.len()..from.len() {
421 tensor.remove_axis(*at)?;
422 }
423 }
424 if to.len() > from.len() {
425 for _ in from.len()..to.len() {
426 tensor.insert_axis(*at)?;
427 }
428 }
429 Ok(())
430 } else {
431 bail!(
432 "Invalid reshaping: {:?} on tensor {:?} (broadcasting allowed: {:?})",
433 self,
434 tensor,
435 broadcasting
436 )
437 }
438 }
439 }
440 }
441
442 pub fn change_view<D>(&self, view: &mut ArrayViewD<D>) -> TractResult<()> {
443 use tract_ndarray::Axis;
444 match *self {
445 AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),
446 AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),
447 AxisOp::Move(from, to) if from < to => {
448 for left in from..to {
449 view.swap_axes(left, left + 1);
450 }
451 }
452 AxisOp::Move(from, to) => {
453 for left in (to..from).rev() {
454 view.swap_axes(left, left + 1);
455 }
456 }
457 AxisOp::Reshape(_, _, _) => bail!("Reshape can not change views in place"),
458 }
459 Ok(())
460 }
461
462 pub fn change_view_mut<D>(&self, view: &mut ArrayViewMutD<D>) -> TractResult<()> {
463 use tract_ndarray::Axis;
464 match *self {
465 AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),
466 AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),
467 AxisOp::Move(from, to) if from < to => {
468 for left in from..to {
469 view.swap_axes(left, left + 1);
470 }
471 }
472 AxisOp::Move(from, to) => {
473 for left in (to..from).rev() {
474 view.swap_axes(left, left + 1);
475 }
476 }
477 AxisOp::Reshape(_, _, _) => bail!("Reshape can not change views in place"),
478 }
479 Ok(())
480 }
481
482 pub fn recip(&self) -> AxisOp {
483 match self.canonical().as_ref() {
484 Add(ix) => Rm(*ix),
485 Rm(ix) => Add(*ix),
486 Move(from, to) if from == to => self.clone(),
487 Move(from, to) if *from + 1 == *to => self.clone(),
488 Move(from, to) if *from == *to + 1 => {
489 unreachable!();
490 }
491 Move(from, to) => Move(*to, *from),
492 Reshape(at, from, to) => Reshape(*at, to.clone(), from.clone()),
493 }
494 }
495
496 pub fn is_noop(&self) -> bool {
497 match self {
498 Move(f, t) if f == t => true,
499 Reshape(_, f, t) if f == t => true,
500 _ => false,
501 }
502 }
503
504 pub fn only_shape(&self) -> bool {
505 if self.is_noop() {
506 return true;
507 }
508 !matches!(self, Move(_, _))
509 }
510
511 pub fn wire_split_axis(
512 model: &mut TypedModel,
513 name: impl ToString,
514 outlet: OutletId,
515 axis: usize,
516 outer_dim: usize,
517 ) -> TractResult<TVec<OutletId>> {
518 let fact = model.outlet_fact(outlet)?;
519 let dim: TDim = fact.shape[axis].clone();
520 let inner_dim = dim.clone() / outer_dim;
521 let op = Self::Reshape(axis, tvec!(dim.clone()), tvec!(outer_dim.to_dim(), inner_dim));
522 model.wire_node(name.to_string(), op, &[outlet])
523 }
524
525 pub fn wire_collapse_axis(
526 model: &mut TypedModel,
527 name: impl ToString,
528 outlet: OutletId,
529 axis: usize,
530 ) -> TractResult<TVec<OutletId>> {
531 let fact = model.outlet_fact(outlet)?;
532 let dim: TDim = fact.shape[axis].clone();
533 let next_dim: TDim = fact.shape[axis + 1].clone();
534 let op = Self::Reshape(axis, tvec!(dim.clone(), next_dim.clone()), tvec!(dim * next_dim));
535 model.wire_node(name.to_string(), op, &[outlet])
536 }
537
538 #[inline]
539 pub fn required_rank(&self) -> usize {
540 match self {
541 Rm(r) => r + 1,
542 Add(a) => *a,
543 Reshape(at, from, _to) => at + from.len(),
544 Move(from, to) => *from.max(to),
545 }
546 }
547
548 pub fn pad_left(&self, prefix: usize) -> AxisOp {
553 match self {
554 Rm(r) => Rm(r + prefix),
555 Add(a) => Add(a + prefix),
556 Reshape(at, from, to) => Reshape(at + prefix, from.clone(), to.clone()),
557 Move(from, to) => Move(from + prefix, to + prefix),
558 }
559 }
560
561 pub fn trim_left(&self, prefix: usize) -> TractResult<AxisOp> {
562 Ok(match self {
563 Rm(r) if *r >= prefix => Rm(r - prefix),
564 Add(a) if *a >= prefix => Add(a - prefix),
565 Reshape(at, from, to) if *at >= prefix => {
566 Reshape(at - prefix, from.clone(), to.clone())
567 }
568 Move(from, to) if *from >= prefix && *to >= prefix => Move(from - prefix, to - prefix),
569 _ => bail!("Can no trim left {self:?} by {prefix}"),
570 })
571 }
572}
573
574pub fn wire_rank_broadcast(
575 prefix: impl AsRef<str>,
576 target: &mut TypedModel,
577 inputs: &[OutletId],
578) -> TractResult<TVec<OutletId>> {
579 let facts =
580 inputs.iter().map(|o| target.outlet_fact(*o).cloned()).collect::<TractResult<TVec<_>>>()?;
581 let max_rank = facts.iter().map(|f| f.rank()).max().unwrap();
582 let mut wires = tvec!();
583 for i in 0..inputs.len() {
584 let mut wire = inputs[i];
585 for _ in facts[i].rank()..max_rank {
586 let name = target.unique_name(prefix.as_ref().to_string() + ".fix-rank");
587 wire = target.wire_node(name, AxisOp::Add(0), &[wire])?[0];
588 }
589 wires.push(wire);
590 }
591 Ok(wires)
592}
593
594pub fn wire_with_rank_broadcast(
595 prefix: impl AsRef<str>,
596 target: &mut TypedModel,
597 op: impl Into<Box<dyn TypedOp>>,
598 inputs: &[OutletId],
599) -> TractResult<TVec<OutletId>> {
600 let prefix = prefix.as_ref();
601 let wires = wire_rank_broadcast(prefix, target, inputs)?;
602 target.wire_node(prefix, op.into(), &wires)
603}
604
605#[derive(Clone, Debug, PartialEq, Eq, Hash)]
606pub struct AxisChange {
607 pub outlet: OutletId,
608 pub op: AxisOp,
609}
610
611#[derive(Clone, Default, Debug)]
612pub struct AxisChangeConsequence {
613 pub substitute_op: Option<Box<dyn TypedOp>>,
614 pub wire_changes: TVec<(InOut, AxisOp)>,
615}
616
617impl AxisChangeConsequence {
618 pub fn new(
619 _model: &TypedModel,
620 node: &TypedNode,
621 op: Option<Box<dyn TypedOp>>,
622 axis_op: &AxisOp,
623 ) -> AxisChangeConsequence {
624 let mut wire_changes = tvec!();
625 for i in 0..node.inputs.len() {
626 wire_changes.push((InOut::In(i), axis_op.clone()));
627 }
628 for i in 0..node.outputs.len() {
629 wire_changes.push((InOut::Out(i), axis_op.clone()));
630 }
631 AxisChangeConsequence { wire_changes, substitute_op: op }
632 }
633}
634
635impl Op for AxisOp {
636 fn name(&self) -> StaticName {
637 match self {
638 Add(_) => "AddAxis".into(),
639 Rm(_) => "RmAxis".into(),
640 Move(_, _) => "MoveAxis".into(),
641 Reshape(_, _, _) => "Reshape".into(),
642 }
643 }
644
645 fn info(&self) -> TractResult<Vec<String>> {
646 match self {
647 Add(axis) | Rm(axis) => Ok(vec![format!("Axis: {axis}")]),
648 Move(from, to) => Ok(vec![format!("Axis {from} to {to}")]),
649 Reshape(at, from, to) => Ok(vec![format!(
650 "Axes starting at {}: {:?} to {:?}",
651 at,
652 from.iter().join(","),
653 to.iter().join(",")
654 )]),
655 }
656 }
657
658 op_as_typed_op!();
659}
660
661impl EvalOp for AxisOp {
662 op_out_of_plan!();
663
664 fn eval(&self, ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
665 let mut input = args_1!(inputs).into_tensor();
666 match self {
667 AxisOp::Reshape(skip, from, to) => {
668 let from = from.iter().map(|d| d.eval(ctx.symbols)).collect();
669 let to = to.iter().map(|d| d.eval(ctx.symbols)).collect();
670 AxisOp::Reshape(*skip, from, to).change_tensor(&mut input, false)?
671 }
672 _ => self.change_tensor(&mut input, false)?,
673 }
674 Ok(tvec!(input.into_tvalue()))
675 }
676}
677
678fn remap_uniform_tdim(expr: &TDim, axis_op: &AxisOp) -> Option<TDim> {
682 let syms = expr.symbols();
683 let coord_syms: Vec<(usize, Symbol)> = syms
684 .into_iter()
685 .filter_map(|s| {
686 let name = format!("{s}");
687 name.strip_prefix("🎯").and_then(|rest| rest.parse::<usize>().ok()).map(|k| (k, s))
688 })
689 .collect();
690
691 if coord_syms.is_empty() {
692 return Some(expr.clone());
694 }
695
696 if let AxisOp::Reshape(at, from_dims, to_dims) = axis_op.canonical().as_ref() {
697 if from_dims.iter().all(|d| d.is_one()) && to_dims.iter().all(|d| d.is_one()) {
699 return Some(expr.clone());
700 }
701 if from_dims.len() == 1 {
707 let from_dim = from_dims[0].clone();
708 let to_product: TDim = to_dims.iter().fold(TDim::Val(1), |acc, d| acc * d.clone());
709 if to_product == from_dim {
710 let k_to = to_dims.len();
711 let mut map: HashMap<Symbol, TDim> = HashMap::default();
712 for (k, sym) in &coord_syms {
713 let scope = sym.scope()?;
714 let new_expr = if *k < *at {
715 TDim::Sym(sym.clone())
716 } else if *k == *at {
717 let mut sum = TDim::Val(0);
718 let mut stride = TDim::Val(1);
719 for i in (0..k_to).rev() {
720 let new_sym = scope.coord_sym(*at + i);
721 sum += TDim::Sym(new_sym) * stride.clone();
722 stride *= to_dims[i].clone();
723 }
724 sum
725 } else {
726 TDim::Sym(scope.coord_sym(*k + k_to - 1))
727 };
728 map.insert(sym.clone(), new_expr);
729 }
730 return expr.substitute_all(&map).ok().map(|e| e.reduce());
731 }
732 }
733 if to_dims.len() == 1 {
739 let to_dim = to_dims[0].clone();
740 let from_product: TDim = from_dims.iter().fold(TDim::Val(1), |acc, d| acc * d.clone());
741 if from_product == to_dim {
742 let k_from = from_dims.len();
743 let mut map: HashMap<Symbol, TDim> = HashMap::default();
744 for (k, sym) in &coord_syms {
745 let scope = sym.scope()?;
746 let new_expr = if *k < *at {
747 TDim::Sym(sym.clone())
748 } else if *k < *at + k_from {
749 let i = *k - *at;
750 let only_nontrivial =
751 from_dims.iter().enumerate().all(|(j, d)| j == i || d.is_one());
752 if only_nontrivial {
753 TDim::Sym(scope.coord_sym(*at))
754 } else {
755 return None;
756 }
757 } else {
758 TDim::Sym(scope.coord_sym(*k - (k_from - 1)))
759 };
760 map.insert(sym.clone(), new_expr);
761 }
762 return expr.substitute_all(&map).ok().map(|e| e.reduce());
763 }
764 }
765 return None;
766 }
767
768 let map: HashMap<Symbol, TDim> = coord_syms
771 .into_iter()
772 .filter_map(|(k, sym)| {
773 let new_k = axis_op.transform_axis(k)?;
774 if new_k == k {
775 return None;
776 }
777 let scope = sym.scope()?;
778 Some((sym, TDim::Sym(scope.coord_sym(new_k))))
779 })
780 .collect();
781 if map.is_empty() {
782 return Some(expr.clone());
783 }
784 expr.substitute_all(&map).ok()
785}
786
787impl TypedOp for AxisOp {
788 as_op!();
789
790 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
791 if let Some(bqf) =
792 inputs[0].exotic_fact().and_then(|of| of.downcast_ref::<BlockQuantFact>())
793 {
794 let mut new_shape: TVec<usize> = bqf.shape().into();
795 self.change_shape_array(&mut new_shape, false)?;
796 let new_bqf = BlockQuantFact::new(bqf.format.clone(), new_shape.clone());
797 let shape: TVec<TDim> = new_shape.iter().map(|d| d.to_dim()).collect();
798 let mut new_fact = inputs[0].datum_type.fact(&*shape).with_exotic_fact(new_bqf);
799 if let Some(k) = &inputs[0].konst {
800 let mut new = k.clone().into_tensor();
801 self.change_tensor(&mut new, false)?;
802 new_fact.konst = Some(new.into());
803 }
804 return Ok(tvec!(new_fact));
805 }
806 let mut shape = inputs[0].shape.clone();
807 self.change_shape(&mut shape, false)?;
808 let mut fact = inputs[0].datum_type.fact(shape);
809 fact.exotic_fact.clone_from(&inputs[0].exotic_fact);
810 if let Some(tdim) = &inputs[0].uniform_tdim {
811 fact.uniform_tdim = remap_uniform_tdim(tdim, self);
812 }
813 Ok(tvec!(fact))
814 }
815
816 fn input_roi(
817 &self,
818 model: &TypedModel,
819 node: &TypedNode,
820 ) -> TractResult<Option<TVec<Option<TDim>>>> {
821 crate::optim::propagate_roi::bubble_roi(model, node)
822 }
823
824 fn axes_mapping(
825 &self,
826 inputs: &[&TypedFact],
827 outputs: &[&TypedFact],
828 ) -> TractResult<AxesMapping> {
829 let mut axes: Vec<Axis> = (0..inputs[0].rank())
830 .zip('a'..)
831 .map(|(axis_id, repr)| {
832 let mut axis = Axis::new(repr, inputs.len(), outputs.len()).input(0, axis_id);
833 if let Some(out) = self.transform_axis(axis_id) {
834 axis = axis.output(0, out);
835 }
836 axis
837 })
838 .collect();
839 for (axis, letter) in (0..outputs[0].rank()).zip('A'..) {
840 if self.recip().transform_axis(axis).is_none() {
841 axes.push(Axis::new(letter, inputs.len(), outputs.len()).output(0, axis));
842 }
843 }
844 AxesMapping::new(inputs.len(), outputs.len(), axes)
845 }
846
847 fn declutter(
848 &self,
849 model: &TypedModel,
850 node: &TypedNode,
851 ) -> TractResult<Option<TypedModelPatch>> {
852 if self.is_noop()
853 && let Some(p) = TypedModelPatch::shunt_one_op(model, node)?
854 {
855 return Ok(Some(p));
856 }
857 let simplified = self.simplify();
858 if simplified.len() != 1 || &simplified[0] != self {
859 let mut patch = TypedModelPatch::default();
860 let mut wire = patch.tap_model(model, node.inputs[0])?;
861 for (ix, op) in simplified.into_iter().enumerate() {
862 wire = patch.wire_node(format!("{}.{}", node.name, ix), op, &[wire])?[0];
863 }
864 patch.shunt_outside(model, node.id.into(), wire)?;
865 Ok(Some(patch))
866 } else {
867 Ok(None)
868 }
869 }
870
871 fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {
872 Ok(tvec!((InOut::Out(0), self.recip()), (InOut::In(0), self.clone())))
873 }
874
875 fn change_axes(
876 &self,
877 _model: &TypedModel,
878 _node: &TypedNode,
879 io: InOut,
880 change: &AxisOp,
881 ) -> TractResult<Option<AxisChangeConsequence>> {
882 let op = if let InOut::Out(0) = io {
883 rule_if_some!(more = self.recip().change_axes(_model, _node, InOut::In(0), change)?);
884 AxisChangeConsequence {
885 substitute_op: more.substitute_op.map(|op| {
886 if let Some(op) = op.as_op().downcast_ref::<AxisOp>() {
887 Box::new(op.recip())
888 } else {
889 op }
891 }),
892 wire_changes: more
893 .wire_changes
894 .into_iter()
895 .map(|wc| {
896 (if wc.0 == InOut::In(0) { InOut::Out(0) } else { InOut::In(0) }, wc.1)
897 })
898 .collect(),
899 }
900 } else if change == self {
901 AxisChangeConsequence { substitute_op: Some(Box::new(Identity)), wire_changes: tvec!() }
902 } else {
903 rule_if_some!((new_op, new_change) = self.merge_incoming_change(change));
904 trace!(" Change:{change:?} self:{self:?} -> change:{new_change:?} op:{new_op:?}");
905 let substitute_op: Box<dyn TypedOp> =
906 if let Some(o) = new_op { Box::new(o) as _ } else { Box::new(Identity) };
907 let mut wire_changes = tvec!();
908 if !change.is_noop() {
909 wire_changes.push((InOut::In(0), change.clone()))
910 }
911 if let Some(new_change) = new_change {
912 wire_changes.push((InOut::Out(0), new_change))
913 }
914 AxisChangeConsequence { substitute_op: Some(substitute_op), wire_changes }
915 };
916 Ok(Some(op))
917 }
918
919 fn set_symbols(
920 &self,
921 _source: &TypedModel,
922 node: &TypedNode,
923 target: &mut TypedModel,
924 mapping: &HashMap<OutletId, OutletId>,
925 subs: &HashMap<Symbol, TDim>,
926 ) -> TractResult<TVec<OutletId>> {
927 let op = if let AxisOp::Reshape(axis, from, to) = self {
928 AxisOp::Reshape(
929 *axis,
930 from.iter().map(|d| d.substitute_all(subs)).collect::<TractResult<_>>()?,
931 to.iter().map(|d| d.substitute_all(subs)).collect::<TractResult<_>>()?,
932 )
933 } else {
934 self.clone()
935 };
936 target.wire_node(&node.name, op, &[mapping[&node.inputs[0]]])
937 }
938
939 fn slice(
940 &self,
941 patch: &mut TypedModelPatch,
942 _model: &TypedModel,
943 node: &TypedNode,
944 _prefix: &str,
945 inputs: &[OutletId],
946 output_axis: usize,
947 _start: &TDim,
948 _end: &TDim,
949 ) -> TractResult<Option<TVec<OutletId>>> {
950 if let Reshape(pos, _from, to) = self
952 && output_axis >= *pos
953 && output_axis < pos + to.len()
954 {
955 return Ok(None);
956 }
957 patch.wire_node(&node.name, &node.op, inputs).map(Some)
958 }
959
960 fn codegen(
961 &self,
962 model: &TypedModel,
963 node: &TypedNode,
964 ) -> TractResult<Option<TypedModelPatch>> {
965 rule_if!(node.outputs[0].fact.exotic_fact.is_none());
966 if let Some(shape) = node.outputs[0].fact.shape.as_concrete()
967 && !matches!(self, AxisOp::Move(_, _))
968 {
969 let (inputs, outputs) = model.node_facts(node.id)?;
970 let mapping = self.axes_mapping(&inputs, &outputs)?;
971 let op = IntoShape {
972 mapping,
973 len: shape.iter().product(),
974 strides: Tensor::natural_strides(shape),
975 dims: shape.into(),
976 };
977 return Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, op)?));
978 }
979 Ok(None)
980 }
981}
982
983fn perm_to_cycles(perm: &[usize]) -> TVec<TVec<usize>> {
985 let mut cycles: TVec<TVec<usize>> = tvec!();
986 let mut done = 0;
987 while done < perm.len() {
988 if perm[done] == done || cycles.iter().any(|c| c.contains(&done)) {
989 done += 1;
990 continue;
991 }
992 let mut cycle = tvec!();
993 let mut current = done;
994 loop {
995 cycle.push(current);
996 current = perm[current];
997 if current == done {
998 break;
999 }
1000 }
1001 cycles.push(cycle)
1002 }
1003 cycles
1004}
1005
1006fn is_rotation_cycle(cycle: &[usize]) -> Option<(usize, usize)> {
1007 if cycle.windows(2).all(|w| w[0] + 1 == w[1]) {
1008 Some((cycle[0], cycle[cycle.len() - 1]))
1009 } else if cycle[1..cycle.len()].windows(2).all(|w| w[0] - 1 == w[1])
1010 && cycle[cycle.len() - 1] - 1 == cycle[0]
1011 {
1012 Some((cycle[1], cycle[0]))
1013 } else {
1014 None
1015 }
1016}
1017
1018fn perm_to_atoms(input: &[usize]) -> TVec<(usize, usize)> {
1019 let mut changes: TVec<(usize, usize)> = tvec!();
1020 'top: loop {
1021 let mut reached: TVec<usize> = (0..input.len()).collect();
1022 changes.iter().for_each(|(f, t)| {
1023 let axis = reached.remove(*f);
1024 reached.insert(*t, axis);
1025 });
1026 if &*reached == input {
1027 return changes;
1028 }
1029 let remaining: TVec<usize> =
1030 input.iter().map(|x| reached.iter().position(|y| y == x).unwrap()).collect();
1031 let cycles = perm_to_cycles(&remaining);
1032 for cycle in &cycles {
1033 if let Some(rot) = is_rotation_cycle(cycle) {
1034 changes.push(rot);
1035 continue 'top;
1036 }
1037 }
1038 changes.push((cycles[0][1], cycles[0][0]));
1039 }
1040}
1041
1042pub fn perm_to_ops(input: &[usize]) -> TVec<AxisOp> {
1043 perm_to_atoms(input).into_iter().map(|pair| AxisOp::Move(pair.0, pair.1)).collect()
1044}
1045
1046pub fn compute_shape_with_tf_rules(input: &[TDim], shape_spec: &[TDim]) -> TractResult<TVec<TDim>> {
1047 compute_shape_with_onnx_rules(input, shape_spec, false)
1048}
1049
1050pub fn compute_shape_with_onnx_rules(
1057 input: &[TDim],
1058 shape_spec: &[TDim],
1059 allowzero: bool,
1060) -> TractResult<TVec<TDim>> {
1061 let mut shape: TVec<TDim> = shape_spec.into();
1062 if !allowzero {
1063 for (i, s) in shape.iter_mut().enumerate() {
1065 if *s == 0.into() {
1066 *s = input
1067 .get(i)
1068 .with_context(|| {
1069 format!(
1070 "Reshape: 0 at position {i} but input only has {} dims",
1071 input.len()
1072 )
1073 })?
1074 .clone();
1075 }
1076 }
1077 }
1078 let input_vol: TDim = input.iter().product();
1079 if let Some(pos) = shape.iter().position(|d| *d == (-1).into()) {
1080 if allowzero && shape.iter().any(|d| *d == 0.into()) {
1084 bail!(
1085 "Reshape: allowzero is set, so the target shape {shape_spec:?} may not contain both 0 and -1"
1086 )
1087 }
1088 let shape_vol: TDim = shape.iter().filter(|d| **d != (-1).into()).product();
1089 if shape_vol == 0.into() {
1092 bail!(
1093 "Reshape: -1 in target shape {shape_spec:?} cannot be inferred, because the other \
1094 dimensions of {shape:?} leave a volume of zero"
1095 )
1096 }
1097 let div = input_vol.maybe_div(&shape_vol)?;
1098 if div.1 != 1 {
1099 bail!("invalid")
1100 }
1101 shape[pos] = div.0;
1102 } else {
1103 let shape_vol: TDim = shape.iter().product();
1104 if input_vol != shape_vol {
1105 bail!(
1106 "Reshape volume mismatch: input {input:?} (vol={input_vol}) vs shape {shape:?} (vol={shape_vol})"
1107 );
1108 }
1109 }
1110 if let Some(bad) = shape.iter().position(|d| d.as_i64().map(|d| d < 0).unwrap_or(false)) {
1113 bail!(
1114 "Reshape: dimension {bad} of target shape {shape_spec:?} is still negative after \
1115 inference (resolved to {shape:?}); at most one dimension may be -1"
1116 )
1117 }
1118 Ok(shape)
1119}
1120
1121pub fn to_axis_ops_with_tf_rules(
1122 input_orig: &[TDim],
1123 output_spec: &[TDim],
1124) -> TractResult<TVec<AxisOp>> {
1125 to_axis_ops_with_onnx_rules(input_orig, output_spec, false)
1126}
1127
1128pub fn to_axis_ops_with_onnx_rules(
1130 input_orig: &[TDim],
1131 output_spec: &[TDim],
1132 allowzero: bool,
1133) -> TractResult<TVec<AxisOp>> {
1134 let final_output = compute_shape_with_onnx_rules(input_orig, output_spec, allowzero)?;
1135 if final_output.iter().chain(input_orig.iter()).any(|d| *d == 0.into()) {
1139 return Ok(if *input_orig == *final_output {
1140 tvec!()
1141 } else {
1142 tvec!(AxisOp::Reshape(0, input_orig.into(), final_output.clone()))
1143 });
1144 }
1145 let mut stack: TVec<AxisOp> = tvec!();
1146 'top: loop {
1147 let current_input =
1148 stack.iter().try_fold(TVec::from(input_orig), |mut shape, op| -> TractResult<_> {
1149 op.change_shape_array(&mut shape, false)?;
1150 Ok(shape)
1151 })?;
1152 if current_input == final_output {
1153 return Ok(stack);
1154 }
1155 if let Some(common) =
1156 current_input.iter().zip(final_output.iter()).position(|(a, b)| a != b)
1157 {
1158 if current_input[common].is_one() {
1159 stack.push(AxisOp::Rm(common));
1160 } else if final_output[common].is_one() {
1161 stack.push(AxisOp::Add(common));
1162 } else {
1163 for i in common..current_input.len() {
1166 let i_group = ¤t_input[common..i + 1];
1167 let i_volume: TDim = i_group.iter().product();
1168 for o in common..final_output.len() {
1169 let o_group = &final_output[common..o + 1];
1170 let o_volume: TDim = o_group.iter().product();
1171 if i_volume == o_volume {
1172 stack.push(AxisOp::Reshape(common, i_group.into(), o_group.into()));
1173 continue 'top;
1174 }
1175 }
1176 }
1177 bail!(
1178 "Could not find matching reshape grouping: current_input={current_input:?} final_output={final_output:?} common={common}"
1179 )
1180 }
1181 } else if final_output.len() > current_input.len() {
1182 stack.push(AxisOp::Add(current_input.len()));
1183 } else {
1184 stack.push(AxisOp::Rm(current_input.len() - 1));
1185 }
1186 }
1187}
1188
1189#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1190pub struct IntoShape {
1191 pub mapping: AxesMapping,
1192 pub len: usize,
1193 pub dims: TVec<usize>,
1194 pub strides: TVec<isize>,
1195}
1196
1197impl Op for IntoShape {
1198 fn name(&self) -> StaticName {
1199 "IntoShape".into()
1200 }
1201
1202 fn info(&self) -> TractResult<Vec<String>> {
1203 Ok(vec![format!("{}", self.mapping)])
1204 }
1205
1206 op_as_typed_op!();
1207}
1208
1209impl EvalOp for IntoShape {
1210 op_out_of_plan!();
1211
1212 fn eval(&self, _ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
1213 let mut input = args_1!(inputs).into_tensor();
1214 ensure!(input.len() == self.len);
1215 unsafe { input.set_geometry_unchecked(&self.dims, &self.strides) };
1216 Ok(tvec!(input.into_tvalue()))
1217 }
1218}
1219
1220impl TypedOp for IntoShape {
1221 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
1222 let mut fact = inputs[0].datum_type.fact(&self.dims);
1223 if let Some(of) = &inputs[0].exotic_fact {
1224 fact = fact.with_exotic_fact(of.clone());
1225 }
1226 Ok(tvec!(fact))
1227 }
1228
1229 fn declutter(
1230 &self,
1231 model: &TypedModel,
1232 node: &TypedNode,
1233 ) -> TractResult<Option<TypedModelPatch>> {
1234 let input = model.outlet_fact(node.inputs[0])?;
1235 if input.shape.as_concrete().is_some_and(|shape| shape == &*self.dims) {
1236 return TypedModelPatch::shunt_one_op(model, node);
1237 }
1238 if let Some(succ) = model.single_succ(node.id)?
1239 && let Some(into_shape) = succ.op_as::<IntoShape>()
1240 {
1241 let op =
1242 Self { mapping: self.mapping.compose(&into_shape.mapping)?, ..into_shape.clone() };
1243 return Ok(Some(TypedModelPatch::fuse_with_next(model, node, op)?));
1244 }
1245 Ok(None)
1246 }
1247
1248 as_op!();
1249}
1250
1251#[cfg(test)]
1252mod test {
1253 use super::*;
1254
1255 #[test]
1256 fn test_perm_to_cycles() {
1257 assert_eq!(perm_to_cycles(&[1, 2, 0]), tvec!(tvec!(0, 1, 2)));
1258 assert_eq!(perm_to_cycles(&[2, 0, 1]), tvec!(tvec!(0, 2, 1)));
1259 assert_eq!(perm_to_cycles(&[1, 2, 3, 0]), tvec!(tvec!(0, 1, 2, 3)));
1260 assert_eq!(perm_to_cycles(&[3, 0, 1, 2]), tvec!(tvec!(0, 3, 2, 1)));
1261 assert_eq!(perm_to_cycles(&[3, 1, 2, 0, 4]), tvec!(tvec!(0, 3)));
1262 }
1263
1264 #[test]
1265 fn is_rotation() {
1266 assert_eq!(is_rotation_cycle(&[0, 1, 2]), Some((0, 2)));
1267 assert_eq!(is_rotation_cycle(&[0, 2, 1]), Some((2, 0)));
1268 }
1269
1270 #[test]
1271 fn test_perm_one_rotation() {
1272 assert_eq!(perm_to_atoms(&[1, 2, 0, 3, 4]), tvec!((0, 2)));
1273 }
1274
1275 #[test]
1276 fn test_perm_two_rotations() {
1277 assert_eq!(perm_to_atoms(&[1, 2, 0, 4, 3]), tvec!((0, 2), (3, 4)));
1278 }
1279
1280 #[test]
1281 fn test_perm_complex() {
1282 assert_eq!(perm_to_atoms(&[3, 1, 2, 0, 4]), tvec!((3, 0), (1, 3)));
1283 }
1284
1285 #[test]
1292 pub fn transform_op_add_0_add_0() {
1293 let change = Add(0);
1294 let op = Add(0);
1295 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(1)))));
1296 }
1297
1298 #[test]
1303 pub fn transform_op_add_0_add_1() {
1304 let change = Add(0);
1305 let op = Add(1);
1306 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(2)), Some(Add(0)))));
1307 }
1308
1309 #[test]
1314 pub fn transform_op_add_1_add_0() {
1315 let change = Add(1);
1316 let op = Add(0);
1317 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(2)))));
1318 }
1319
1320 #[test]
1325 pub fn transform_op_rm_0_rm_1() {
1326 let change = Rm(0);
1327 let op = Rm(1);
1328 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));
1329 }
1330
1331 #[test]
1336 pub fn transform_op_rm_1_rm_0() {
1337 let change = Rm(1);
1338 let op = Rm(0);
1339 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));
1340 }
1341
1342 #[test]
1349 pub fn transform_op_add_0_rm_0() {
1350 let change = Add(0);
1351 let op = Rm(0);
1352 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Add(0)))));
1353 }
1354
1355 #[test]
1360 pub fn transform_op_add_0_rm_1() {
1361 let change = Add(0);
1362 let op = Rm(1);
1363 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(2)), Some(Add(0)))));
1364 }
1365
1366 #[test]
1371 pub fn transform_op_add_1_rm_0() {
1372 let change = Add(1);
1373 let op = Rm(0);
1374 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Add(0)))));
1375 }
1376
1377 #[test]
1384 pub fn transform_op_rm_1_add_0() {
1385 let change = Rm(1);
1386 let op = Add(0);
1387 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(2)))));
1388 }
1389
1390 #[test]
1395 pub fn transform_op_rm_0_add_1() {
1396 let change = Rm(0);
1397 let op = Add(1);
1398 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(0)))));
1399 }
1400
1401 #[test]
1406 pub fn transform_op_mv_02_rm_2() {
1407 let change = Move(0, 2);
1408 let op = Rm(2);
1409 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Move(0, 1)))));
1410 }
1411}
1412
1413#[cfg(test)]
1414mod proptests {
1415 use super::*;
1416 use proptest::prelude::*;
1417
1418 #[derive(Debug)]
1419 struct ComposeProblem {
1420 input: TVec<usize>,
1421 ops: TVec<AxisOp>,
1422 }
1423
1424 impl Arbitrary for AxisOp {
1425 type Parameters = TVec<usize>;
1426 type Strategy = BoxedStrategy<AxisOp>;
1427 fn arbitrary_with(shape: TVec<usize>) -> Self::Strategy {
1428 let mut ops: BoxedStrategy<AxisOp> = (0usize..shape.len() + 1).prop_map(Add).boxed();
1429 if shape.len() > 1 {
1430 ops = ops
1431 .prop_union(
1432 (0..shape.len(), 0..shape.len() - 1)
1433 .prop_map(|(a, b)| Move(a, b + (b >= a) as usize))
1434 .boxed(),
1435 )
1436 .boxed()
1437 }
1438 let rms = (0..shape.len()).filter(|&ax| shape[ax] == 1).map(Rm).collect::<Vec<_>>();
1439 if rms.len() > 0 {
1440 ops = ops
1441 .prop_union((0..rms.len()).prop_map(move |rm| rms[rm].clone()).boxed())
1442 .boxed()
1443 }
1444 let mergeable: Vec<AxisOp> = shape
1445 .windows(2)
1446 .enumerate()
1447 .filter(|(_, w)| w[0] > 1 && w[1] > 1)
1448 .map(|(ix, w)| {
1449 Reshape(ix, tvec!(w[0].to_dim(), w[1].to_dim()), tvec!((w[0] * w[1]).to_dim()))
1450 })
1451 .collect();
1452 if mergeable.len() > 1 {
1453 ops = ops
1454 .prop_union(
1455 (0..mergeable.len()).prop_map(move |ix| mergeable[ix].clone()).boxed(),
1456 )
1457 .boxed()
1458 }
1459 ops
1460 }
1461 }
1462
1463 impl Arbitrary for ComposeProblem {
1464 type Parameters = ();
1465 type Strategy = BoxedStrategy<ComposeProblem>;
1466 fn arbitrary_with(_args: ()) -> Self::Strategy {
1467 let input = proptest::collection::vec(1usize..4, 1usize..4);
1468 fn tail(len: usize, shape: TVec<usize>) -> BoxedStrategy<TVec<AxisOp>> {
1469 if len == 0 {
1470 Just(tvec!()).boxed()
1471 } else {
1472 AxisOp::arbitrary_with(shape.clone())
1473 .prop_flat_map(move |op| {
1474 let mut shape = shape.clone();
1475 op.change_shape_array(&mut shape, false).unwrap();
1476 tail(len - 1, shape.clone()).prop_map(move |mut t| {
1477 t.insert(0, op.clone());
1478 t
1479 })
1480 })
1481 .boxed()
1482 }
1483 }
1484 (input, 1usize..=5)
1485 .prop_flat_map(|(input, len)| (Just(input.clone()), tail(len, input.into())))
1486 .prop_map(|(input, ops)| ComposeProblem { input: input.into(), ops })
1487 .boxed()
1488 }
1489 }
1490
1491 impl ComposeProblem {
1492 pub fn model(&self) -> TractResult<TypedModel> {
1493 let mut model = TypedModel::default();
1494 let mut wire = model.add_source("source", i64::fact(&self.input))?;
1495 for (ix, op) in self.ops.iter().enumerate() {
1496 wire = model.wire_node(format!("op_{ix}"), op.clone(), &[wire])?[0];
1497 }
1498 model.select_output_outlets(&[wire])?;
1499 Ok(model)
1500 }
1501
1502 fn input(&self) -> TractResult<Tensor> {
1503 unsafe {
1504 let mut t = Tensor::uninitialized::<i64>(&self.input)?;
1505 for i in 0..t.len() {
1506 t.try_as_plain_ram_mut().unwrap().as_slice_mut().unwrap()[i] = i as i64;
1507 }
1508 Ok(t)
1509 }
1510 }
1511
1512 fn check(&self) -> TractResult<()> {
1513 crate::setup_test_logger();
1514 let input = self.input()?;
1515 let model = self.model()?;
1516 let raw = model.into_runnable()?.run(tvec!(input.clone().into_tvalue()))?;
1517 let optimized = self.model()?.into_decluttered()?;
1518 let opt = optimized.into_runnable()?.run(tvec!(input.into_tvalue()))?;
1519 opt[0].close_enough(&raw[0], false)
1520 }
1521 }
1522
1523 proptest! {
1524 #[test]
1525 fn recip(pb in any::<AxisOp>()) {
1526 assert_eq!(pb.recip().recip(), pb);
1527 }
1528
1529 #[test]
1530 fn axis_ops(pb in any::<ComposeProblem>()) {
1531 pb.check().unwrap()
1532 }
1533 }
1534
1535 #[test]
1536 fn add_0_rm_0() {
1537 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Rm(0)] };
1538 pb.check().unwrap();
1539 }
1540
1541 #[test]
1542 fn add_0_move_01() {
1543 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1)] };
1544 pb.check().unwrap();
1545 }
1546
1547 #[test]
1548 fn add_0_move_01_add_1() {
1549 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1), Add(1)] };
1550 pb.check().unwrap();
1551 }
1552
1553 #[test]
1554 fn recip_move_01() {
1555 let op = Move(1, 0);
1556 assert_eq!(op.recip().recip(), op);
1557 }
1558
1559 #[test]
1560 fn recip_move_20() {
1561 let op = Move(2, 0);
1562 assert_eq!(op.recip().recip(), op);
1563 }
1564
1565 #[test]
1566 fn recip_move_02() {
1567 let op = Move(0, 2);
1568 assert_eq!(op.recip().recip(), op);
1569 }
1570
1571 #[test]
1572 fn add_0_add_1_move_02() {
1573 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(1), Move(0, 2)] };
1574 pb.check().unwrap();
1575 }
1576
1577 #[test]
1578 fn add_0_add_0() {
1579 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0)] };
1580 pb.check().unwrap();
1581 }
1582
1583 #[test]
1584 fn add_0_add_0_move_02() {
1585 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(0, 2)] };
1586 pb.check().unwrap();
1587 }
1588
1589 #[test]
1590 fn add_0_add_2_move_12() {
1591 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(2), Move(1, 2)] };
1592 pb.check().unwrap();
1593 }
1594
1595 #[test]
1596 fn add_0_add_0_move_02_rm_0() {
1597 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0), Move(0, 2), Rm(0)] };
1598 pb.check().unwrap();
1599 }
1600
1601 #[test]
1602 fn add_0_add_0_move_20_move_20() {
1603 let pb =
1604 ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(2, 0), Move(2, 0)] };
1605 pb.check().unwrap();
1606 }
1607
1608 #[test]
1609 fn move_01_add_0() {
1610 let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Move(0, 1), Add(0)] };
1611 pb.check().unwrap();
1612 }
1613
1614 #[test]
1615 fn add_0_move_02_move_02() {
1616 let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Add(0), Move(0, 2), Move(0, 2),] };
1617 pb.check().unwrap();
1618 }
1619
1620 #[test]
1621 fn add_0_add_2_move_20_move_12_rm_2() {
1622 let pb = ComposeProblem {
1623 input: tvec![3],
1624 ops: tvec![Add(0), Add(2), Move(2, 0), Move(1, 2), Rm(2)],
1625 };
1626 pb.check().unwrap();
1627 }
1628
1629 #[test]
1630 fn move_02_move_02() {
1631 let pb = ComposeProblem { input: tvec![2, 1, 1], ops: tvec![Move(0, 2), Move(0, 2)] };
1632 pb.check().unwrap();
1633 }
1634
1635 #[test]
1636 fn rm_1_perm_10_add_0() {
1637 let pb = ComposeProblem { input: tvec![1, 1, 2], ops: tvec![Rm(1), Move(0, 1), Add(0)] };
1638 pb.check().unwrap();
1639 }
1640
1641 #[test]
1642 fn add_2_move_02_move_02() {
1643 let pb = ComposeProblem { input: tvec![3, 2], ops: tvec![Add(2), Move(0, 2), Move(0, 2)] };
1644 pb.check().unwrap();
1645 }
1646
1647 #[test]
1648 fn move_01_move_20_move_20() {
1649 let pb = ComposeProblem {
1650 input: tvec![2, 3, 2],
1651 ops: tvec![Move(0, 1), Move(2, 0), Move(2, 0)],
1652 };
1653 pb.check().unwrap();
1654 }
1655
1656 #[test]
1657 fn reshape_axes_tracking() {
1658 let pb = ComposeProblem {
1659 input: tvec![2, 2, 2],
1660 ops: tvec![Reshape(0, tvec!(2.to_dim(), 2.to_dim()), tvec!(4.to_dim()))],
1661 };
1662 pb.check().unwrap();
1663 }
1664
1665 #[test]
1666 fn simplify_reshape() {
1667 macro_rules! d {
1668 ($($dim: expr),*) => { tvec!($($dim.to_dim()),*) }
1669 }
1670 assert_eq!(Reshape(3, d!(), d!()).simplify(), tvec!());
1671 assert_eq!(Reshape(3, d!(2, 3), d!(2, 3)).simplify(), tvec!());
1672 assert_eq!(Reshape(3, d!(1), d!()).simplify(), tvec!(Rm(3)));
1673 assert_eq!(Reshape(3, d!(), d!(1)).simplify(), tvec!(Add(3)));
1674 assert_eq!(
1675 Reshape(3, d!(2, 3, 4), d!(2, 4, 3)).simplify(),
1676 tvec!(Reshape(4, d!(3, 4), d!(4, 3)))
1677 );
1678 assert_eq!(
1679 Reshape(3, d!(3, 4, 2), d!(4, 3, 2)).simplify(),
1680 tvec!(Reshape(3, d!(3, 4), d!(4, 3)))
1681 );
1682 assert_eq!(
1683 Reshape(3, d!(1, 2, 3), d!(3, 2)).simplify(),
1684 tvec!(Rm(3), Reshape(3, d!(2, 3), d!(3, 2)))
1685 );
1686 assert_eq!(
1687 Reshape(3, d!(2, 3), d!(1, 3, 2)).simplify(),
1688 tvec!(Reshape(3, d!(2, 3), d!(3, 2)), Add(3))
1689 );
1690 assert_eq!(
1691 Reshape(3, d!(2, 3, 1), d!(3, 2)).simplify(),
1692 tvec!(Rm(5), Reshape(3, d!(2, 3), d!(3, 2)))
1693 );
1694 assert_eq!(
1695 Reshape(3, d!(2, 3), d!(3, 2, 1)).simplify(),
1696 tvec!(Add(5), Reshape(3, d!(2, 3), d!(3, 2)))
1697 );
1698 assert_eq!(
1699 Reshape(2, d!(2, 2, 1), d!(4)).simplify(),
1700 tvec!(Rm(4), Reshape(2, d!(2, 2), d!(4)))
1701 );
1702 assert_eq!(Reshape(1, d!(1, 2), d!(2)).simplify(), tvec!(Rm(1)));
1703 }
1704
1705 macro_rules! s {
1706 ($($a:expr),*) => {&[ $($a.clone().into()),* ]}
1707 }
1708
1709 macro_rules! r {
1710 ($at: expr ; $($from:expr),* => $($to:expr),*) => {
1711 AxisOp::Reshape($at, tvec!($($from.into()),*), tvec!($($to.into()),*))
1712 }
1713 }
1714
1715 #[test]
1716 fn compute_invalid() {
1717 assert!(compute_shape_with_tf_rules(s![3, 4, 5], s!(100)).is_err());
1718 }
1719
1720 #[test]
1721 fn compute_with_leading_zero() {
1722 assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(0, 0, 5)).unwrap(), s![3, 4, 5])
1723 }
1724
1725 #[test]
1726 fn compute_with_leading_zero_with_flatten() {
1727 assert_eq!(
1728 &*compute_shape_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),
1729 s![2, 3, 35]
1730 )
1731 }
1732
1733 #[test]
1734 fn compute_with_trailing_zero() {
1735 assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(3, -1, 0)).unwrap(), s![3, 4, 5])
1736 }
1737
1738 #[test]
1743 fn compute_allowzero_keeps_a_literal_zero() {
1744 assert_eq!(&*compute_shape_with_onnx_rules(s![3, 0], s!(0), true).unwrap(), s![0])
1745 }
1746
1747 #[test]
1748 fn compute_without_allowzero_copies_the_input_dim() {
1749 assert!(compute_shape_with_onnx_rules(s![3, 0], s!(0), false).is_err())
1752 }
1753
1754 #[test]
1755 fn compute_allowzero_reordered() {
1756 assert_eq!(
1758 &*compute_shape_with_onnx_rules(s![0, 3, 4], s!(3, 4, 0), true).unwrap(),
1759 s![3, 4, 0]
1760 )
1761 }
1762
1763 #[test]
1764 fn compute_rejects_two_inferred_dims() {
1765 assert!(compute_shape_with_tf_rules(s![2, 3, 4], s!(-1, -1)).is_err())
1766 }
1767
1768 #[test]
1769 fn compute_rejects_three_inferred_dims() {
1770 assert!(compute_shape_with_tf_rules(s![2, 3, 4], s!(-1, -1, -1)).is_err())
1771 }
1772
1773 #[test]
1774 fn compute_rejects_inferred_dim_beside_other_negative() {
1775 assert!(compute_shape_with_tf_rules(s![2, 3, 4], s!(-1, -2)).is_err());
1776 assert!(compute_shape_with_tf_rules(s![2, 3, 4], s!(-2, -1)).is_err())
1777 }
1778
1779 #[test]
1780 fn compute_rejects_negative_that_survives_the_volume_check() {
1781 assert!(compute_shape_with_tf_rules(s![3, 0], s!(-2, 0)).is_err())
1782 }
1783
1784 #[test]
1785 fn compute_rejects_inferred_dim_with_zero_volume_remainder() {
1786 assert!(compute_shape_with_tf_rules(s![3, 0], s!(-1, 0)).is_err());
1787 assert!(compute_shape_with_tf_rules(s![0, 3], s!(0, -1)).is_err())
1788 }
1789
1790 #[test]
1791 fn compute_still_infers_a_single_dim() {
1792 assert_eq!(&*compute_shape_with_tf_rules(s![2, 3, 4], s!(-1)).unwrap(), s![24]);
1793 assert_eq!(&*compute_shape_with_tf_rules(s![2, 3, 4], s!(2, -1)).unwrap(), s![2, 12]);
1794 assert_eq!(&*compute_shape_with_tf_rules(s![3, 0], s!(-1)).unwrap(), s![0]);
1795 }
1796
1797 #[test]
1798 fn compute_leaves_symbolic_dims_alone() {
1799 let table = SymbolScope::default();
1800 let s = table.new_with_prefix("S");
1801 assert_eq!(&*compute_shape_with_tf_rules(s![s, 2, 128], s!(0, -1)).unwrap(), s![s, 256]);
1802 }
1803
1804 #[test]
1805 fn compute_allowzero_rejects_zero_beside_minus_one() {
1806 assert!(compute_shape_with_onnx_rules(s![2, 3], s!(0, -1), true).is_err())
1809 }
1810
1811 #[test]
1812 fn to_axis_ops_allowzero_keeps_a_literal_zero() {
1813 assert!(to_axis_ops_with_onnx_rules(s![3, 0], s!(0), true).is_ok())
1815 }
1816
1817 #[test]
1818 fn compute_bug_1() {
1819 let table = SymbolScope::default();
1820 let s = table.new_with_prefix("S");
1821 assert_eq!(
1822 &*compute_shape_with_tf_rules(s![s, 1, 2, 128], s!(0, 0, -1)).unwrap(),
1823 s![s, 1, 256]
1824 )
1825 }
1826
1827 #[test]
1828 fn compute_bug_2() {
1829 let table = SymbolScope::default();
1830 let b = table.new_with_prefix("B");
1831 let s = table.new_with_prefix("S");
1832 assert_eq!(
1833 &*compute_shape_with_tf_rules(s![s, b, 2, 128], s!(0, 0, -1)).unwrap(),
1834 s![s, b, 256]
1835 )
1836 }
1837
1838 #[test]
1839 fn compute_zero_with_rank_change() {
1840 assert_eq!(
1842 &*compute_shape_with_tf_rules(s![1, 52, 8, 32], s!(0, 0, 8, 16, 2)).unwrap(),
1843 s![1, 52, 8, 16, 2]
1844 )
1845 }
1846
1847 #[test]
1848 fn axis_op_rm_begin() {
1849 assert_eq!(&*to_axis_ops_with_tf_rules(s![1, 2, 3], s!(2, 3)).unwrap(), &[Rm(0)])
1850 }
1851
1852 #[test]
1853 fn axis_op_rm_end() {
1854 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3, 1], s!(2, 3)).unwrap(), &[Rm(2)])
1855 }
1856
1857 #[test]
1858 fn axis_op_insert_begin() {
1859 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(1, 2, 3)).unwrap(), &[Add(0)])
1860 }
1861
1862 #[test]
1863 fn axis_op_insert_end() {
1864 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(2, 3, 1)).unwrap(), &[Add(2)])
1865 }
1866
1867 #[test]
1868 fn axis_op_merge() {
1869 assert_eq!(
1870 &*to_axis_ops_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),
1871 &[r!(2 ; 5,7 => 35 )]
1872 )
1873 }
1874
1875 #[test]
1876 fn axis_op_complex() {
1877 assert_eq!(
1878 &*to_axis_ops_with_tf_rules(s![1, 2, 3, 5, 7], s!(2, 1, 3, 35, 1)).unwrap(),
1879 &[Rm(0), Add(1), r!(3 ; 5,7 => 35 ), Add(4)]
1880 )
1881 }
1882}