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 let mut tmp = tensor.clone().move_axis(*from, *to)?;
409 std::mem::swap(tensor, &mut tmp);
410 Ok(())
411 }
412 Reshape(at, from, to) => {
413 let mut shape: TVec<usize> = tensor.shape().into();
414 self.change_shape_array(&mut shape, true)?;
415 if tensor.set_shape(&shape).is_ok() {
416 Ok(())
417 } else if broadcasting
418 && tensor.shape().iter().skip(*at).take(from.len()).all(|d| *d == 1)
419 {
420 if from.len() > to.len() {
421 for _ in to.len()..from.len() {
422 tensor.remove_axis(*at)?;
423 }
424 }
425 if to.len() > from.len() {
426 for _ in from.len()..to.len() {
427 tensor.insert_axis(*at)?;
428 }
429 }
430 Ok(())
431 } else {
432 bail!(
433 "Invalid reshaping: {:?} on tensor {:?} (broadcasting allowed: {:?})",
434 self,
435 tensor,
436 broadcasting
437 )
438 }
439 }
440 }
441 }
442
443 pub fn change_view<D>(&self, view: &mut ArrayViewD<D>) -> TractResult<()> {
444 use tract_ndarray::Axis;
445 match *self {
446 AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),
447 AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),
448 AxisOp::Move(from, to) if from < to => {
449 for left in from..to {
450 view.swap_axes(left, left + 1);
451 }
452 }
453 AxisOp::Move(from, to) => {
454 for left in (to..from).rev() {
455 view.swap_axes(left, left + 1);
456 }
457 }
458 AxisOp::Reshape(_, _, _) => bail!("Reshape can not change views in place"),
459 }
460 Ok(())
461 }
462
463 pub fn change_view_mut<D>(&self, view: &mut ArrayViewMutD<D>) -> TractResult<()> {
464 use tract_ndarray::Axis;
465 match *self {
466 AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),
467 AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),
468 AxisOp::Move(from, to) if from < to => {
469 for left in from..to {
470 view.swap_axes(left, left + 1);
471 }
472 }
473 AxisOp::Move(from, to) => {
474 for left in (to..from).rev() {
475 view.swap_axes(left, left + 1);
476 }
477 }
478 AxisOp::Reshape(_, _, _) => bail!("Reshape can not change views in place"),
479 }
480 Ok(())
481 }
482
483 pub fn recip(&self) -> AxisOp {
484 match self.canonical().as_ref() {
485 Add(ix) => Rm(*ix),
486 Rm(ix) => Add(*ix),
487 Move(from, to) if from == to => self.clone(),
488 Move(from, to) if *from + 1 == *to => self.clone(),
489 Move(from, to) if *from == *to + 1 => {
490 unreachable!();
491 }
492 Move(from, to) => Move(*to, *from),
493 Reshape(at, from, to) => Reshape(*at, to.clone(), from.clone()),
494 }
495 }
496
497 pub fn is_noop(&self) -> bool {
498 match self {
499 Move(f, t) if f == t => true,
500 Reshape(_, f, t) if f == t => true,
501 _ => false,
502 }
503 }
504
505 pub fn only_shape(&self) -> bool {
506 if self.is_noop() {
507 return true;
508 }
509 !matches!(self, Move(_, _))
510 }
511
512 pub fn wire_split_axis(
513 model: &mut TypedModel,
514 name: impl ToString,
515 outlet: OutletId,
516 axis: usize,
517 outer_dim: usize,
518 ) -> TractResult<TVec<OutletId>> {
519 let fact = model.outlet_fact(outlet)?;
520 let dim: TDim = fact.shape[axis].clone();
521 let inner_dim = dim.clone() / outer_dim;
522 let op = Self::Reshape(axis, tvec!(dim.clone()), tvec!(outer_dim.to_dim(), inner_dim));
523 model.wire_node(name.to_string(), op, &[outlet])
524 }
525
526 pub fn wire_collapse_axis(
527 model: &mut TypedModel,
528 name: impl ToString,
529 outlet: OutletId,
530 axis: usize,
531 ) -> TractResult<TVec<OutletId>> {
532 let fact = model.outlet_fact(outlet)?;
533 let dim: TDim = fact.shape[axis].clone();
534 let next_dim: TDim = fact.shape[axis + 1].clone();
535 let op = Self::Reshape(axis, tvec!(dim.clone(), next_dim.clone()), tvec!(dim * next_dim));
536 model.wire_node(name.to_string(), op, &[outlet])
537 }
538
539 #[inline]
540 pub fn required_rank(&self) -> usize {
541 match self {
542 Rm(r) => r + 1,
543 Add(a) => *a,
544 Reshape(at, from, _to) => at + from.len(),
545 Move(from, to) => *from.max(to),
546 }
547 }
548
549 pub fn trim_left(&self, prefix: usize) -> TractResult<AxisOp> {
550 Ok(match self {
551 Rm(r) if *r >= prefix => Rm(r - prefix),
552 Add(a) if *a >= prefix => Add(a - prefix),
553 Reshape(at, from, to) if *at >= prefix => {
554 Reshape(at - prefix, from.clone(), to.clone())
555 }
556 Move(from, to) if *from >= prefix && *to >= prefix => Move(from - prefix, to - prefix),
557 _ => bail!("Can no trim left {self:?} by {prefix}"),
558 })
559 }
560}
561
562pub fn wire_rank_broadcast(
563 prefix: impl AsRef<str>,
564 target: &mut TypedModel,
565 inputs: &[OutletId],
566) -> TractResult<TVec<OutletId>> {
567 let facts =
568 inputs.iter().map(|o| target.outlet_fact(*o).cloned()).collect::<TractResult<TVec<_>>>()?;
569 let max_rank = facts.iter().map(|f| f.rank()).max().unwrap();
570 let mut wires = tvec!();
571 for i in 0..inputs.len() {
572 let mut wire = inputs[i];
573 for _ in facts[i].rank()..max_rank {
574 let name = target.unique_name(prefix.as_ref().to_string() + ".fix-rank");
575 wire = target.wire_node(name, AxisOp::Add(0), &[wire])?[0];
576 }
577 wires.push(wire);
578 }
579 Ok(wires)
580}
581
582pub fn wire_with_rank_broadcast(
583 prefix: impl AsRef<str>,
584 target: &mut TypedModel,
585 op: impl Into<Box<dyn TypedOp>>,
586 inputs: &[OutletId],
587) -> TractResult<TVec<OutletId>> {
588 let prefix = prefix.as_ref();
589 let wires = wire_rank_broadcast(prefix, target, inputs)?;
590 target.wire_node(prefix, op.into(), &wires)
591}
592
593#[derive(Clone, Debug, PartialEq, Eq, Hash)]
594pub struct AxisChange {
595 pub outlet: OutletId,
596 pub op: AxisOp,
597}
598
599#[derive(Clone, Default, Debug)]
600pub struct AxisChangeConsequence {
601 pub substitute_op: Option<Box<dyn TypedOp>>,
602 pub wire_changes: TVec<(InOut, AxisOp)>,
603}
604
605impl AxisChangeConsequence {
606 pub fn new(
607 _model: &TypedModel,
608 node: &TypedNode,
609 op: Option<Box<dyn TypedOp>>,
610 axis_op: &AxisOp,
611 ) -> AxisChangeConsequence {
612 let mut wire_changes = tvec!();
613 for i in 0..node.inputs.len() {
614 wire_changes.push((InOut::In(i), axis_op.clone()));
615 }
616 for i in 0..node.outputs.len() {
617 wire_changes.push((InOut::Out(i), axis_op.clone()));
618 }
619 AxisChangeConsequence { wire_changes, substitute_op: op }
620 }
621}
622
623impl Op for AxisOp {
624 fn name(&self) -> StaticName {
625 match self {
626 Add(_) => "AddAxis".into(),
627 Rm(_) => "RmAxis".into(),
628 Move(_, _) => "MoveAxis".into(),
629 Reshape(_, _, _) => "Reshape".into(),
630 }
631 }
632
633 fn info(&self) -> TractResult<Vec<String>> {
634 match self {
635 Add(axis) | Rm(axis) => Ok(vec![format!("Axis: {axis}")]),
636 Move(from, to) => Ok(vec![format!("Axis {from} to {to}")]),
637 Reshape(at, from, to) => Ok(vec![format!(
638 "Axes starting at {}: {:?} to {:?}",
639 at,
640 from.iter().join(","),
641 to.iter().join(",")
642 )]),
643 }
644 }
645
646 op_as_typed_op!();
647}
648
649impl EvalOp for AxisOp {
650 fn is_stateless(&self) -> bool {
651 true
652 }
653
654 fn eval_with_session(
655 &self,
656 _node_id: usize,
657 session: &TurnState,
658 inputs: TVec<TValue>,
659 ) -> TractResult<TVec<TValue>> {
660 let mut input = args_1!(inputs).into_tensor();
661 match self {
662 AxisOp::Reshape(skip, from, to) => {
663 let from = from.iter().map(|d| d.eval(&session.resolved_symbols)).collect();
664 let to = to.iter().map(|d| d.eval(&session.resolved_symbols)).collect();
665 AxisOp::Reshape(*skip, from, to).change_tensor(&mut input, false)?
666 }
667 _ => self.change_tensor(&mut input, false)?,
668 }
669 Ok(tvec!(input.into_tvalue()))
670 }
671}
672
673fn remap_uniform_tdim(expr: &TDim, axis_op: &AxisOp) -> Option<TDim> {
677 let syms = expr.symbols();
678 let coord_syms: Vec<(usize, Symbol)> = syms
679 .into_iter()
680 .filter_map(|s| {
681 let name = format!("{s}");
682 name.strip_prefix("🎯").and_then(|rest| rest.parse::<usize>().ok()).map(|k| (k, s))
683 })
684 .collect();
685
686 if coord_syms.is_empty() {
687 return Some(expr.clone());
689 }
690
691 if let AxisOp::Reshape(at, from_dims, to_dims) = axis_op.canonical().as_ref() {
692 if from_dims.iter().all(|d| d.is_one()) && to_dims.iter().all(|d| d.is_one()) {
694 return Some(expr.clone());
695 }
696 if from_dims.len() == 1 {
702 let from_dim = from_dims[0].clone();
703 let to_product: TDim = to_dims.iter().fold(TDim::Val(1), |acc, d| acc * d.clone());
704 if to_product == from_dim {
705 let k_to = to_dims.len();
706 let mut map: HashMap<Symbol, TDim> = HashMap::default();
707 for (k, sym) in &coord_syms {
708 let scope = sym.scope()?;
709 let new_expr = if *k < *at {
710 TDim::Sym(sym.clone())
711 } else if *k == *at {
712 let mut sum = TDim::Val(0);
713 let mut stride = TDim::Val(1);
714 for i in (0..k_to).rev() {
715 let new_sym = scope.coord_sym(*at + i);
716 sum += TDim::Sym(new_sym) * stride.clone();
717 stride *= to_dims[i].clone();
718 }
719 sum
720 } else {
721 TDim::Sym(scope.coord_sym(*k + k_to - 1))
722 };
723 map.insert(sym.clone(), new_expr);
724 }
725 return expr.substitute_all(&map).ok().map(|e| e.reduce());
726 }
727 }
728 if to_dims.len() == 1 {
734 let to_dim = to_dims[0].clone();
735 let from_product: TDim = from_dims.iter().fold(TDim::Val(1), |acc, d| acc * d.clone());
736 if from_product == to_dim {
737 let k_from = from_dims.len();
738 let mut map: HashMap<Symbol, TDim> = HashMap::default();
739 for (k, sym) in &coord_syms {
740 let scope = sym.scope()?;
741 let new_expr = if *k < *at {
742 TDim::Sym(sym.clone())
743 } else if *k < *at + k_from {
744 let i = *k - *at;
745 let only_nontrivial =
746 from_dims.iter().enumerate().all(|(j, d)| j == i || d.is_one());
747 if only_nontrivial {
748 TDim::Sym(scope.coord_sym(*at))
749 } else {
750 return None;
751 }
752 } else {
753 TDim::Sym(scope.coord_sym(*k - (k_from - 1)))
754 };
755 map.insert(sym.clone(), new_expr);
756 }
757 return expr.substitute_all(&map).ok().map(|e| e.reduce());
758 }
759 }
760 return None;
761 }
762
763 let map: HashMap<Symbol, TDim> = coord_syms
766 .into_iter()
767 .filter_map(|(k, sym)| {
768 let new_k = axis_op.transform_axis(k)?;
769 if new_k == k {
770 return None;
771 }
772 let scope = sym.scope()?;
773 Some((sym, TDim::Sym(scope.coord_sym(new_k))))
774 })
775 .collect();
776 if map.is_empty() {
777 return Some(expr.clone());
778 }
779 expr.substitute_all(&map).ok()
780}
781
782impl TypedOp for AxisOp {
783 as_op!();
784
785 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
786 if let Some(bqf) =
787 inputs[0].exotic_fact().and_then(|of| of.downcast_ref::<BlockQuantFact>())
788 {
789 let mut new_shape: TVec<usize> = bqf.shape().into();
790 self.change_shape_array(&mut new_shape, false)?;
791 let new_bqf = BlockQuantFact::new(bqf.format.clone(), new_shape.clone());
792 let shape: TVec<TDim> = new_shape.iter().map(|d| d.to_dim()).collect();
793 let mut new_fact = inputs[0].datum_type.fact(&*shape).with_exotic_fact(new_bqf);
794 if let Some(k) = &inputs[0].konst {
795 let mut new = k.clone().into_tensor();
796 self.change_tensor(&mut new, false)?;
797 new_fact.konst = Some(new.into());
798 }
799 return Ok(tvec!(new_fact));
800 }
801 let mut shape = inputs[0].shape.clone();
802 self.change_shape(&mut shape, false)?;
803 let mut fact = inputs[0].datum_type.fact(shape);
804 fact.exotic_fact.clone_from(&inputs[0].exotic_fact);
805 if let Some(tdim) = &inputs[0].uniform_tdim {
806 fact.uniform_tdim = remap_uniform_tdim(tdim, self);
807 }
808 Ok(tvec!(fact))
809 }
810
811 fn input_roi(
812 &self,
813 model: &TypedModel,
814 node: &TypedNode,
815 ) -> TractResult<Option<TVec<Option<TDim>>>> {
816 crate::optim::propagate_roi::bubble_roi(model, node)
817 }
818
819 fn axes_mapping(
820 &self,
821 inputs: &[&TypedFact],
822 outputs: &[&TypedFact],
823 ) -> TractResult<AxesMapping> {
824 let mut axes: Vec<Axis> = (0..inputs[0].rank())
825 .zip('a'..)
826 .map(|(axis_id, repr)| {
827 let mut axis = Axis::new(repr, inputs.len(), outputs.len()).input(0, axis_id);
828 if let Some(out) = self.transform_axis(axis_id) {
829 axis = axis.output(0, out);
830 }
831 axis
832 })
833 .collect();
834 for (axis, letter) in (0..outputs[0].rank()).zip('A'..) {
835 if self.recip().transform_axis(axis).is_none() {
836 axes.push(Axis::new(letter, inputs.len(), outputs.len()).output(0, axis));
837 }
838 }
839 AxesMapping::new(inputs.len(), outputs.len(), axes)
840 }
841
842 fn declutter(
843 &self,
844 model: &TypedModel,
845 node: &TypedNode,
846 ) -> TractResult<Option<TypedModelPatch>> {
847 if self.is_noop()
848 && let Some(p) = TypedModelPatch::shunt_one_op(model, node)?
849 {
850 return Ok(Some(p));
851 }
852 let simplified = self.simplify();
853 if simplified.len() != 1 || &simplified[0] != self {
854 let mut patch = TypedModelPatch::default();
855 let mut wire = patch.tap_model(model, node.inputs[0])?;
856 for (ix, op) in simplified.into_iter().enumerate() {
857 wire = patch.wire_node(format!("{}.{}", node.name, ix), op, &[wire])?[0];
858 }
859 patch.shunt_outside(model, node.id.into(), wire)?;
860 Ok(Some(patch))
861 } else {
862 Ok(None)
863 }
864 }
865
866 fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {
867 Ok(tvec!((InOut::Out(0), self.recip()), (InOut::In(0), self.clone())))
868 }
869
870 fn change_axes(
871 &self,
872 _model: &TypedModel,
873 _node: &TypedNode,
874 io: InOut,
875 change: &AxisOp,
876 ) -> TractResult<Option<AxisChangeConsequence>> {
877 let op = if let InOut::Out(0) = io {
878 rule_if_some!(more = self.recip().change_axes(_model, _node, InOut::In(0), change)?);
879 AxisChangeConsequence {
880 substitute_op: more.substitute_op.map(|op| {
881 if let Some(op) = op.as_op().downcast_ref::<AxisOp>() {
882 Box::new(op.recip())
883 } else {
884 op }
886 }),
887 wire_changes: more
888 .wire_changes
889 .into_iter()
890 .map(|wc| {
891 (if wc.0 == InOut::In(0) { InOut::Out(0) } else { InOut::In(0) }, wc.1)
892 })
893 .collect(),
894 }
895 } else if change == self {
896 AxisChangeConsequence { substitute_op: Some(Box::new(Identity)), wire_changes: tvec!() }
897 } else {
898 rule_if_some!((new_op, new_change) = self.merge_incoming_change(change));
899 trace!(" Change:{change:?} self:{self:?} -> change:{new_change:?} op:{new_op:?}");
900 let substitute_op: Box<dyn TypedOp> =
901 if let Some(o) = new_op { Box::new(o) as _ } else { Box::new(Identity) };
902 let mut wire_changes = tvec!();
903 if !change.is_noop() {
904 wire_changes.push((InOut::In(0), change.clone()))
905 }
906 if let Some(new_change) = new_change {
907 wire_changes.push((InOut::Out(0), new_change))
908 }
909 AxisChangeConsequence { substitute_op: Some(substitute_op), wire_changes }
910 };
911 Ok(Some(op))
912 }
913
914 fn set_symbols(
915 &self,
916 _source: &TypedModel,
917 node: &TypedNode,
918 target: &mut TypedModel,
919 mapping: &HashMap<OutletId, OutletId>,
920 subs: &HashMap<Symbol, TDim>,
921 ) -> TractResult<TVec<OutletId>> {
922 let op = if let AxisOp::Reshape(axis, from, to) = self {
923 AxisOp::Reshape(
924 *axis,
925 from.iter().map(|d| d.substitute_all(subs)).collect::<TractResult<_>>()?,
926 to.iter().map(|d| d.substitute_all(subs)).collect::<TractResult<_>>()?,
927 )
928 } else {
929 self.clone()
930 };
931 target.wire_node(&node.name, op, &[mapping[&node.inputs[0]]])
932 }
933
934 fn slice(
935 &self,
936 patch: &mut TypedModelPatch,
937 _model: &TypedModel,
938 node: &TypedNode,
939 _prefix: &str,
940 inputs: &[OutletId],
941 output_axis: usize,
942 _start: &TDim,
943 _end: &TDim,
944 ) -> TractResult<Option<TVec<OutletId>>> {
945 if let Reshape(pos, _from, to) = self
947 && output_axis >= *pos
948 && output_axis < pos + to.len()
949 {
950 return Ok(None);
951 }
952 patch.wire_node(&node.name, &node.op, inputs).map(Some)
953 }
954
955 fn codegen(
956 &self,
957 model: &TypedModel,
958 node: &TypedNode,
959 ) -> TractResult<Option<TypedModelPatch>> {
960 rule_if!(node.outputs[0].fact.exotic_fact.is_none());
961 if let Some(shape) = node.outputs[0].fact.shape.as_concrete()
962 && !matches!(self, AxisOp::Move(_, _))
963 {
964 let (inputs, outputs) = model.node_facts(node.id)?;
965 let mapping = self.axes_mapping(&inputs, &outputs)?;
966 let op = IntoShape {
967 mapping,
968 len: shape.iter().product(),
969 strides: Tensor::natural_strides(shape),
970 dims: shape.into(),
971 };
972 return Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, op)?));
973 }
974 Ok(None)
975 }
976}
977
978fn perm_to_cycles(perm: &[usize]) -> TVec<TVec<usize>> {
980 let mut cycles: TVec<TVec<usize>> = tvec!();
981 let mut done = 0;
982 while done < perm.len() {
983 if perm[done] == done || cycles.iter().any(|c| c.contains(&done)) {
984 done += 1;
985 continue;
986 }
987 let mut cycle = tvec!();
988 let mut current = done;
989 loop {
990 cycle.push(current);
991 current = perm[current];
992 if current == done {
993 break;
994 }
995 }
996 cycles.push(cycle)
997 }
998 cycles
999}
1000
1001fn is_rotation_cycle(cycle: &[usize]) -> Option<(usize, usize)> {
1002 if cycle.windows(2).all(|w| w[0] + 1 == w[1]) {
1003 Some((cycle[0], cycle[cycle.len() - 1]))
1004 } else if cycle[1..cycle.len()].windows(2).all(|w| w[0] - 1 == w[1])
1005 && cycle[cycle.len() - 1] - 1 == cycle[0]
1006 {
1007 Some((cycle[1], cycle[0]))
1008 } else {
1009 None
1010 }
1011}
1012
1013fn perm_to_atoms(input: &[usize]) -> TVec<(usize, usize)> {
1014 let mut changes: TVec<(usize, usize)> = tvec!();
1015 'top: loop {
1016 let mut reached: TVec<usize> = (0..input.len()).collect();
1017 changes.iter().for_each(|(f, t)| {
1018 let axis = reached.remove(*f);
1019 reached.insert(*t, axis);
1020 });
1021 if &*reached == input {
1022 return changes;
1023 }
1024 let remaining: TVec<usize> =
1025 input.iter().map(|x| reached.iter().position(|y| y == x).unwrap()).collect();
1026 let cycles = perm_to_cycles(&remaining);
1027 for cycle in &cycles {
1028 if let Some(rot) = is_rotation_cycle(cycle) {
1029 changes.push(rot);
1030 continue 'top;
1031 }
1032 }
1033 changes.push((cycles[0][1], cycles[0][0]));
1034 }
1035}
1036
1037pub fn perm_to_ops(input: &[usize]) -> TVec<AxisOp> {
1038 perm_to_atoms(input).into_iter().map(|pair| AxisOp::Move(pair.0, pair.1)).collect()
1039}
1040
1041pub fn compute_shape_with_tf_rules(input: &[TDim], shape_spec: &[TDim]) -> TractResult<TVec<TDim>> {
1042 let mut shape: TVec<TDim> = shape_spec.into();
1043 for (i, s) in shape.iter_mut().enumerate() {
1045 if *s == 0.into() {
1046 *s = input
1047 .get(i)
1048 .with_context(|| {
1049 format!("Reshape: 0 at position {i} but input only has {} dims", input.len())
1050 })?
1051 .clone();
1052 }
1053 }
1054 let input_vol: TDim = input.iter().product();
1055 if let Some(pos) = shape.iter().position(|d| *d == (-1).into()) {
1056 let shape_vol: TDim = shape.iter().filter(|d| **d != (-1).into()).product();
1057 let div = input_vol.maybe_div(&shape_vol)?;
1058 if div.1 != 1 {
1059 bail!("invalid")
1060 }
1061 shape[pos] = div.0;
1062 } else {
1063 let shape_vol: TDim = shape.iter().product();
1064 if input_vol != shape_vol {
1065 bail!(
1066 "Reshape volume mismatch: input {input:?} (vol={input_vol}) vs shape {shape:?} (vol={shape_vol})"
1067 );
1068 }
1069 }
1070 Ok(shape)
1071}
1072
1073pub fn to_axis_ops_with_tf_rules(
1074 input_orig: &[TDim],
1075 output_spec: &[TDim],
1076) -> TractResult<TVec<AxisOp>> {
1077 let final_output = compute_shape_with_tf_rules(input_orig, output_spec)?;
1078 let mut stack: TVec<AxisOp> = tvec!();
1079 'top: loop {
1080 let current_input =
1081 stack.iter().try_fold(TVec::from(input_orig), |mut shape, op| -> TractResult<_> {
1082 op.change_shape_array(&mut shape, false)?;
1083 Ok(shape)
1084 })?;
1085 if current_input == final_output {
1086 return Ok(stack);
1087 }
1088 if let Some(common) =
1089 current_input.iter().zip(final_output.iter()).position(|(a, b)| a != b)
1090 {
1091 if current_input[common].is_one() {
1092 stack.push(AxisOp::Rm(common));
1093 } else if final_output[common].is_one() {
1094 stack.push(AxisOp::Add(common));
1095 } else {
1096 for i in common..current_input.len() {
1099 let i_group = ¤t_input[common..i + 1];
1100 let i_volume: TDim = i_group.iter().product();
1101 for o in common..final_output.len() {
1102 let o_group = &final_output[common..o + 1];
1103 let o_volume: TDim = o_group.iter().product();
1104 if i_volume == o_volume {
1105 stack.push(AxisOp::Reshape(common, i_group.into(), o_group.into()));
1106 continue 'top;
1107 }
1108 }
1109 }
1110 bail!(
1111 "Could not find matching reshape grouping: current_input={current_input:?} final_output={final_output:?} common={common}"
1112 )
1113 }
1114 } else if final_output.len() > current_input.len() {
1115 stack.push(AxisOp::Add(current_input.len()));
1116 } else {
1117 stack.push(AxisOp::Rm(current_input.len() - 1));
1118 }
1119 }
1120}
1121
1122#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1123pub struct IntoShape {
1124 pub mapping: AxesMapping,
1125 pub len: usize,
1126 pub dims: TVec<usize>,
1127 pub strides: TVec<isize>,
1128}
1129
1130impl Op for IntoShape {
1131 fn name(&self) -> StaticName {
1132 "IntoShape".into()
1133 }
1134
1135 fn info(&self) -> TractResult<Vec<String>> {
1136 Ok(vec![format!("{}", self.mapping)])
1137 }
1138
1139 op_as_typed_op!();
1140}
1141
1142impl EvalOp for IntoShape {
1143 fn is_stateless(&self) -> bool {
1144 true
1145 }
1146
1147 fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
1148 let mut input = args_1!(inputs).into_tensor();
1149 ensure!(input.len() == self.len);
1150 unsafe { input.set_geometry_unchecked(&self.dims, &self.strides) };
1151 Ok(tvec!(input.into_tvalue()))
1152 }
1153}
1154
1155impl TypedOp for IntoShape {
1156 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
1157 let mut fact = inputs[0].datum_type.fact(&self.dims);
1158 if let Some(of) = &inputs[0].exotic_fact {
1159 fact = fact.with_exotic_fact(of.clone());
1160 }
1161 Ok(tvec!(fact))
1162 }
1163
1164 fn declutter(
1165 &self,
1166 model: &TypedModel,
1167 node: &TypedNode,
1168 ) -> TractResult<Option<TypedModelPatch>> {
1169 let input = model.outlet_fact(node.inputs[0])?;
1170 if input.shape.as_concrete().is_some_and(|shape| shape == &*self.dims) {
1171 return TypedModelPatch::shunt_one_op(model, node);
1172 }
1173 if let Some(succ) = model.single_succ(node.id)?
1174 && let Some(into_shape) = succ.op_as::<IntoShape>()
1175 {
1176 let op =
1177 Self { mapping: self.mapping.compose(&into_shape.mapping)?, ..into_shape.clone() };
1178 return Ok(Some(TypedModelPatch::fuse_with_next(model, node, op)?));
1179 }
1180 Ok(None)
1181 }
1182
1183 as_op!();
1184}
1185
1186#[cfg(test)]
1187mod test {
1188 use super::*;
1189
1190 #[test]
1191 fn test_perm_to_cycles() {
1192 assert_eq!(perm_to_cycles(&[1, 2, 0]), tvec!(tvec!(0, 1, 2)));
1193 assert_eq!(perm_to_cycles(&[2, 0, 1]), tvec!(tvec!(0, 2, 1)));
1194 assert_eq!(perm_to_cycles(&[1, 2, 3, 0]), tvec!(tvec!(0, 1, 2, 3)));
1195 assert_eq!(perm_to_cycles(&[3, 0, 1, 2]), tvec!(tvec!(0, 3, 2, 1)));
1196 assert_eq!(perm_to_cycles(&[3, 1, 2, 0, 4]), tvec!(tvec!(0, 3)));
1197 }
1198
1199 #[test]
1200 fn is_rotation() {
1201 assert_eq!(is_rotation_cycle(&[0, 1, 2]), Some((0, 2)));
1202 assert_eq!(is_rotation_cycle(&[0, 2, 1]), Some((2, 0)));
1203 }
1204
1205 #[test]
1206 fn test_perm_one_rotation() {
1207 assert_eq!(perm_to_atoms(&[1, 2, 0, 3, 4]), tvec!((0, 2)));
1208 }
1209
1210 #[test]
1211 fn test_perm_two_rotations() {
1212 assert_eq!(perm_to_atoms(&[1, 2, 0, 4, 3]), tvec!((0, 2), (3, 4)));
1213 }
1214
1215 #[test]
1216 fn test_perm_complex() {
1217 assert_eq!(perm_to_atoms(&[3, 1, 2, 0, 4]), tvec!((3, 0), (1, 3)));
1218 }
1219
1220 #[test]
1227 pub fn transform_op_add_0_add_0() {
1228 let change = Add(0);
1229 let op = Add(0);
1230 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(1)))));
1231 }
1232
1233 #[test]
1238 pub fn transform_op_add_0_add_1() {
1239 let change = Add(0);
1240 let op = Add(1);
1241 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(2)), Some(Add(0)))));
1242 }
1243
1244 #[test]
1249 pub fn transform_op_add_1_add_0() {
1250 let change = Add(1);
1251 let op = Add(0);
1252 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(2)))));
1253 }
1254
1255 #[test]
1260 pub fn transform_op_rm_0_rm_1() {
1261 let change = Rm(0);
1262 let op = Rm(1);
1263 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));
1264 }
1265
1266 #[test]
1271 pub fn transform_op_rm_1_rm_0() {
1272 let change = Rm(1);
1273 let op = Rm(0);
1274 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));
1275 }
1276
1277 #[test]
1284 pub fn transform_op_add_0_rm_0() {
1285 let change = Add(0);
1286 let op = Rm(0);
1287 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Add(0)))));
1288 }
1289
1290 #[test]
1295 pub fn transform_op_add_0_rm_1() {
1296 let change = Add(0);
1297 let op = Rm(1);
1298 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(2)), Some(Add(0)))));
1299 }
1300
1301 #[test]
1306 pub fn transform_op_add_1_rm_0() {
1307 let change = Add(1);
1308 let op = Rm(0);
1309 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Add(0)))));
1310 }
1311
1312 #[test]
1319 pub fn transform_op_rm_1_add_0() {
1320 let change = Rm(1);
1321 let op = Add(0);
1322 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(2)))));
1323 }
1324
1325 #[test]
1330 pub fn transform_op_rm_0_add_1() {
1331 let change = Rm(0);
1332 let op = Add(1);
1333 assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(0)))));
1334 }
1335
1336 #[test]
1341 pub fn transform_op_mv_02_rm_2() {
1342 let change = Move(0, 2);
1343 let op = Rm(2);
1344 assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Move(0, 1)))));
1345 }
1346}
1347
1348#[cfg(test)]
1349mod proptests {
1350 use super::*;
1351 use proptest::prelude::*;
1352
1353 #[derive(Debug)]
1354 struct ComposeProblem {
1355 input: TVec<usize>,
1356 ops: TVec<AxisOp>,
1357 }
1358
1359 impl Arbitrary for AxisOp {
1360 type Parameters = TVec<usize>;
1361 type Strategy = BoxedStrategy<AxisOp>;
1362 fn arbitrary_with(shape: TVec<usize>) -> Self::Strategy {
1363 let mut ops: BoxedStrategy<AxisOp> = (0usize..shape.len() + 1).prop_map(Add).boxed();
1364 if shape.len() > 1 {
1365 ops = ops
1366 .prop_union(
1367 (0..shape.len(), 0..shape.len() - 1)
1368 .prop_map(|(a, b)| Move(a, b + (b >= a) as usize))
1369 .boxed(),
1370 )
1371 .boxed()
1372 }
1373 let rms = (0..shape.len()).filter(|&ax| shape[ax] == 1).map(Rm).collect::<Vec<_>>();
1374 if rms.len() > 0 {
1375 ops = ops
1376 .prop_union((0..rms.len()).prop_map(move |rm| rms[rm].clone()).boxed())
1377 .boxed()
1378 }
1379 let mergeable: Vec<AxisOp> = shape
1380 .windows(2)
1381 .enumerate()
1382 .filter(|(_, w)| w[0] > 1 && w[1] > 1)
1383 .map(|(ix, w)| {
1384 Reshape(ix, tvec!(w[0].to_dim(), w[1].to_dim()), tvec!((w[0] * w[1]).to_dim()))
1385 })
1386 .collect();
1387 if mergeable.len() > 1 {
1388 ops = ops
1389 .prop_union(
1390 (0..mergeable.len()).prop_map(move |ix| mergeable[ix].clone()).boxed(),
1391 )
1392 .boxed()
1393 }
1394 ops
1395 }
1396 }
1397
1398 impl Arbitrary for ComposeProblem {
1399 type Parameters = ();
1400 type Strategy = BoxedStrategy<ComposeProblem>;
1401 fn arbitrary_with(_args: ()) -> Self::Strategy {
1402 let input = proptest::collection::vec(1usize..4, 1usize..4);
1403 fn tail(len: usize, shape: TVec<usize>) -> BoxedStrategy<TVec<AxisOp>> {
1404 if len == 0 {
1405 Just(tvec!()).boxed()
1406 } else {
1407 AxisOp::arbitrary_with(shape.clone())
1408 .prop_flat_map(move |op| {
1409 let mut shape = shape.clone();
1410 op.change_shape_array(&mut shape, false).unwrap();
1411 tail(len - 1, shape.clone()).prop_map(move |mut t| {
1412 t.insert(0, op.clone());
1413 t
1414 })
1415 })
1416 .boxed()
1417 }
1418 }
1419 (input, 1usize..=5)
1420 .prop_flat_map(|(input, len)| (Just(input.clone()), tail(len, input.into())))
1421 .prop_map(|(input, ops)| ComposeProblem { input: input.into(), ops })
1422 .boxed()
1423 }
1424 }
1425
1426 impl ComposeProblem {
1427 pub fn model(&self) -> TractResult<TypedModel> {
1428 let mut model = TypedModel::default();
1429 let mut wire = model.add_source("source", i64::fact(&self.input))?;
1430 for (ix, op) in self.ops.iter().enumerate() {
1431 wire = model.wire_node(format!("op_{ix}"), op.clone(), &[wire])?[0];
1432 }
1433 model.select_output_outlets(&[wire])?;
1434 Ok(model)
1435 }
1436
1437 fn input(&self) -> TractResult<Tensor> {
1438 unsafe {
1439 let mut t = Tensor::uninitialized::<i64>(&self.input)?;
1440 for i in 0..t.len() {
1441 t.try_as_plain_mut().unwrap().as_slice_mut().unwrap()[i] = i as i64;
1442 }
1443 Ok(t)
1444 }
1445 }
1446
1447 fn check(&self) -> TractResult<()> {
1448 crate::setup_test_logger();
1449 let input = self.input()?;
1450 let model = self.model()?;
1451 let raw = model.into_runnable()?.run(tvec!(input.clone().into_tvalue()))?;
1452 let optimized = self.model()?.into_decluttered()?;
1453 let opt = optimized.into_runnable()?.run(tvec!(input.into_tvalue()))?;
1454 opt[0].close_enough(&raw[0], false)
1455 }
1456 }
1457
1458 proptest! {
1459 #[test]
1460 fn recip(pb in any::<AxisOp>()) {
1461 assert_eq!(pb.recip().recip(), pb);
1462 }
1463
1464 #[test]
1465 fn axis_ops(pb in any::<ComposeProblem>()) {
1466 pb.check().unwrap()
1467 }
1468 }
1469
1470 #[test]
1471 fn add_0_rm_0() {
1472 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Rm(0)] };
1473 pb.check().unwrap();
1474 }
1475
1476 #[test]
1477 fn add_0_move_01() {
1478 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1)] };
1479 pb.check().unwrap();
1480 }
1481
1482 #[test]
1483 fn add_0_move_01_add_1() {
1484 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1), Add(1)] };
1485 pb.check().unwrap();
1486 }
1487
1488 #[test]
1489 fn recip_move_01() {
1490 let op = Move(1, 0);
1491 assert_eq!(op.recip().recip(), op);
1492 }
1493
1494 #[test]
1495 fn recip_move_20() {
1496 let op = Move(2, 0);
1497 assert_eq!(op.recip().recip(), op);
1498 }
1499
1500 #[test]
1501 fn recip_move_02() {
1502 let op = Move(0, 2);
1503 assert_eq!(op.recip().recip(), op);
1504 }
1505
1506 #[test]
1507 fn add_0_add_1_move_02() {
1508 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(1), Move(0, 2)] };
1509 pb.check().unwrap();
1510 }
1511
1512 #[test]
1513 fn add_0_add_0() {
1514 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0)] };
1515 pb.check().unwrap();
1516 }
1517
1518 #[test]
1519 fn add_0_add_0_move_02() {
1520 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(0, 2)] };
1521 pb.check().unwrap();
1522 }
1523
1524 #[test]
1525 fn add_0_add_2_move_12() {
1526 let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(2), Move(1, 2)] };
1527 pb.check().unwrap();
1528 }
1529
1530 #[test]
1531 fn add_0_add_0_move_02_rm_0() {
1532 let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0), Move(0, 2), Rm(0)] };
1533 pb.check().unwrap();
1534 }
1535
1536 #[test]
1537 fn add_0_add_0_move_20_move_20() {
1538 let pb =
1539 ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(2, 0), Move(2, 0)] };
1540 pb.check().unwrap();
1541 }
1542
1543 #[test]
1544 fn move_01_add_0() {
1545 let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Move(0, 1), Add(0)] };
1546 pb.check().unwrap();
1547 }
1548
1549 #[test]
1550 fn add_0_move_02_move_02() {
1551 let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Add(0), Move(0, 2), Move(0, 2),] };
1552 pb.check().unwrap();
1553 }
1554
1555 #[test]
1556 fn add_0_add_2_move_20_move_12_rm_2() {
1557 let pb = ComposeProblem {
1558 input: tvec![3],
1559 ops: tvec![Add(0), Add(2), Move(2, 0), Move(1, 2), Rm(2)],
1560 };
1561 pb.check().unwrap();
1562 }
1563
1564 #[test]
1565 fn move_02_move_02() {
1566 let pb = ComposeProblem { input: tvec![2, 1, 1], ops: tvec![Move(0, 2), Move(0, 2)] };
1567 pb.check().unwrap();
1568 }
1569
1570 #[test]
1571 fn rm_1_perm_10_add_0() {
1572 let pb = ComposeProblem { input: tvec![1, 1, 2], ops: tvec![Rm(1), Move(0, 1), Add(0)] };
1573 pb.check().unwrap();
1574 }
1575
1576 #[test]
1577 fn add_2_move_02_move_02() {
1578 let pb = ComposeProblem { input: tvec![3, 2], ops: tvec![Add(2), Move(0, 2), Move(0, 2)] };
1579 pb.check().unwrap();
1580 }
1581
1582 #[test]
1583 fn move_01_move_20_move_20() {
1584 let pb = ComposeProblem {
1585 input: tvec![2, 3, 2],
1586 ops: tvec![Move(0, 1), Move(2, 0), Move(2, 0)],
1587 };
1588 pb.check().unwrap();
1589 }
1590
1591 #[test]
1592 fn reshape_axes_tracking() {
1593 let pb = ComposeProblem {
1594 input: tvec![2, 2, 2],
1595 ops: tvec![Reshape(0, tvec!(2.to_dim(), 2.to_dim()), tvec!(4.to_dim()))],
1596 };
1597 pb.check().unwrap();
1598 }
1599
1600 #[test]
1601 fn simplify_reshape() {
1602 macro_rules! d {
1603 ($($dim: expr),*) => { tvec!($($dim.to_dim()),*) }
1604 }
1605 assert_eq!(Reshape(3, d!(), d!()).simplify(), tvec!());
1606 assert_eq!(Reshape(3, d!(2, 3), d!(2, 3)).simplify(), tvec!());
1607 assert_eq!(Reshape(3, d!(1), d!()).simplify(), tvec!(Rm(3)));
1608 assert_eq!(Reshape(3, d!(), d!(1)).simplify(), tvec!(Add(3)));
1609 assert_eq!(
1610 Reshape(3, d!(2, 3, 4), d!(2, 4, 3)).simplify(),
1611 tvec!(Reshape(4, d!(3, 4), d!(4, 3)))
1612 );
1613 assert_eq!(
1614 Reshape(3, d!(3, 4, 2), d!(4, 3, 2)).simplify(),
1615 tvec!(Reshape(3, d!(3, 4), d!(4, 3)))
1616 );
1617 assert_eq!(
1618 Reshape(3, d!(1, 2, 3), d!(3, 2)).simplify(),
1619 tvec!(Rm(3), Reshape(3, d!(2, 3), d!(3, 2)))
1620 );
1621 assert_eq!(
1622 Reshape(3, d!(2, 3), d!(1, 3, 2)).simplify(),
1623 tvec!(Reshape(3, d!(2, 3), d!(3, 2)), Add(3))
1624 );
1625 assert_eq!(
1626 Reshape(3, d!(2, 3, 1), d!(3, 2)).simplify(),
1627 tvec!(Rm(5), Reshape(3, d!(2, 3), d!(3, 2)))
1628 );
1629 assert_eq!(
1630 Reshape(3, d!(2, 3), d!(3, 2, 1)).simplify(),
1631 tvec!(Add(5), Reshape(3, d!(2, 3), d!(3, 2)))
1632 );
1633 assert_eq!(
1634 Reshape(2, d!(2, 2, 1), d!(4)).simplify(),
1635 tvec!(Rm(4), Reshape(2, d!(2, 2), d!(4)))
1636 );
1637 assert_eq!(Reshape(1, d!(1, 2), d!(2)).simplify(), tvec!(Rm(1)));
1638 }
1639
1640 macro_rules! s {
1641 ($($a:expr),*) => {&[ $($a.clone().into()),* ]}
1642 }
1643
1644 macro_rules! r {
1645 ($at: expr ; $($from:expr),* => $($to:expr),*) => {
1646 AxisOp::Reshape($at, tvec!($($from.into()),*), tvec!($($to.into()),*))
1647 }
1648 }
1649
1650 #[test]
1651 fn compute_invalid() {
1652 assert!(compute_shape_with_tf_rules(s![3, 4, 5], s!(100)).is_err());
1653 }
1654
1655 #[test]
1656 fn compute_with_leading_zero() {
1657 assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(0, 0, 5)).unwrap(), s![3, 4, 5])
1658 }
1659
1660 #[test]
1661 fn compute_with_leading_zero_with_flatten() {
1662 assert_eq!(
1663 &*compute_shape_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),
1664 s![2, 3, 35]
1665 )
1666 }
1667
1668 #[test]
1669 fn compute_with_trailing_zero() {
1670 assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(3, -1, 0)).unwrap(), s![3, 4, 5])
1671 }
1672
1673 #[test]
1674 fn compute_bug_1() {
1675 let table = SymbolScope::default();
1676 let s = table.new_with_prefix("S");
1677 assert_eq!(
1678 &*compute_shape_with_tf_rules(s![s, 1, 2, 128], s!(0, 0, -1)).unwrap(),
1679 s![s, 1, 256]
1680 )
1681 }
1682
1683 #[test]
1684 fn compute_bug_2() {
1685 let table = SymbolScope::default();
1686 let b = table.new_with_prefix("B");
1687 let s = table.new_with_prefix("S");
1688 assert_eq!(
1689 &*compute_shape_with_tf_rules(s![s, b, 2, 128], s!(0, 0, -1)).unwrap(),
1690 s![s, b, 256]
1691 )
1692 }
1693
1694 #[test]
1695 fn compute_zero_with_rank_change() {
1696 assert_eq!(
1698 &*compute_shape_with_tf_rules(s![1, 52, 8, 32], s!(0, 0, 8, 16, 2)).unwrap(),
1699 s![1, 52, 8, 16, 2]
1700 )
1701 }
1702
1703 #[test]
1704 fn axis_op_rm_begin() {
1705 assert_eq!(&*to_axis_ops_with_tf_rules(s![1, 2, 3], s!(2, 3)).unwrap(), &[Rm(0)])
1706 }
1707
1708 #[test]
1709 fn axis_op_rm_end() {
1710 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3, 1], s!(2, 3)).unwrap(), &[Rm(2)])
1711 }
1712
1713 #[test]
1714 fn axis_op_insert_begin() {
1715 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(1, 2, 3)).unwrap(), &[Add(0)])
1716 }
1717
1718 #[test]
1719 fn axis_op_insert_end() {
1720 assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(2, 3, 1)).unwrap(), &[Add(2)])
1721 }
1722
1723 #[test]
1724 fn axis_op_merge() {
1725 assert_eq!(
1726 &*to_axis_ops_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),
1727 &[r!(2 ; 5,7 => 35 )]
1728 )
1729 }
1730
1731 #[test]
1732 fn axis_op_complex() {
1733 assert_eq!(
1734 &*to_axis_ops_with_tf_rules(s![1, 2, 3, 5, 7], s!(2, 1, 3, 35, 1)).unwrap(),
1735 &[Rm(0), Add(1), r!(3 ; 5,7 => 35 ), Add(4)]
1736 )
1737 }
1738}