tract_core/ops/array/
gather_elements.rs1use crate::internal::*;
2use ndarray::*;
3
4#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]
9pub struct GatherElements {
10 pub axis: usize,
11}
12
13impl Op for GatherElements {
14 fn name(&self) -> StaticName {
15 "GatherElements".into()
16 }
17
18 op_as_typed_op!();
19}
20
21impl GatherElements {
22 fn eval_contiguous_last_axis<T: Datum>(
32 &self,
33 data: &ArrayViewD<T>,
34 indices: &ArrayViewD<i64>,
35 ) -> TractResult<Option<ArrayD<T>>> {
36 let rank = data.ndim();
37 let Some(last_axis) = rank.checked_sub(1) else { return Ok(None) };
38 if self.axis != last_axis
39 || indices.ndim() != rank
40 || data.shape()[..last_axis] != indices.shape()[..last_axis]
41 {
42 return Ok(None);
43 }
44 let (Some(data_slice), Some(index_slice)) = (data.as_slice(), indices.as_slice()) else {
47 return Ok(None);
48 };
49 let row_len = data.shape()[last_axis];
50 let gathered_len = indices.shape()[last_axis];
51 let rows = if indices.is_empty() { 0 } else { indices.len() / gathered_len };
54 let mut output = Vec::with_capacity(indices.len());
55 for row in 0..rows {
56 let data_row = &data_slice[row * row_len..][..row_len];
57 for &index in &index_slice[row * gathered_len..][..gathered_len] {
58 let resolved = if index < 0 { index + row_len as i64 } else { index };
59 let value = usize::try_from(resolved)
60 .ok()
61 .and_then(|resolved| data_row.get(resolved))
62 .with_context(|| {
63 format!(
64 "Invalid GatherElements index {index} in row {row} on axis of len {row_len}"
65 )
66 })?;
67 output.push(value.clone());
68 }
69 }
70 Ok(Some(ArrayD::from_shape_vec(indices.shape(), output)?))
71 }
72
73 unsafe fn eval_t<T: Datum>(
74 &self,
75 data: TValue,
76 indices: &ArrayViewD<i64>,
77 ) -> TractResult<TValue> {
78 let data_plain = data.try_as_plain()?;
79 let data_view = unsafe { data_plain.to_array_view_unchecked::<T>() };
80 let output = match self.eval_contiguous_last_axis::<T>(&data_view, indices)? {
81 Some(output) => output,
82 None => ArrayD::<T>::from_shape_fn(indices.shape(), |mut coords| {
83 let index = indices[&coords];
84 coords[self.axis] =
85 if index < 0 { index + data_view.shape()[self.axis] as i64 } else { index }
86 as usize;
87 data_view[coords].clone()
88 }),
89 };
90 let mut tensor = output.into_tensor();
91 unsafe { tensor.set_datum_type(data.datum_type()) };
92 Ok(tensor.into_tvalue())
93 }
94}
95
96impl TypedOp for GatherElements {
97 as_op!();
98
99 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
100 ensure!(
101 inputs[0].rank() == inputs[1].rank(),
102 "GatherElements data and indices must have the same rank, got {} and {}",
103 inputs[0].rank(),
104 inputs[1].rank()
105 );
106 ensure!(
107 self.axis < inputs[0].rank(),
108 "GatherElements axis {} is out of range for rank {}",
109 self.axis,
110 inputs[0].rank()
111 );
112 Ok(tvec!(inputs[0].datum_type.fact(&*inputs[1].shape)))
113 }
114}
115
116impl EvalOp for GatherElements {
117 fn is_stateless(&self) -> bool {
118 true
119 }
120
121 fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
122 let (data, indices) = args_2!(inputs);
123 let indices = indices.cast_to::<i64>()?;
124 let indices = indices.to_plain_array_view::<i64>()?;
125 unsafe {
126 Ok(tvec!(dispatch_datum_by_size!(Self::eval_t(data.datum_type())(
127 self, data, &indices
128 ))?))
129 }
130 }
131}
132
133#[cfg(test)]
137mod tests {
138 use super::*;
139
140 fn gather(data_shape: &[usize], indices: &[i64]) -> TractResult<TValue> {
141 let len = data_shape.iter().product::<usize>();
142 let data = Tensor::from_shape(data_shape, &(0..len).map(|i| i as f32).collect::<Vec<_>>())?;
143 let indices = Tensor::from_shape(&[1, indices.len()], indices)?;
144 let mut outputs = GatherElements::new(data_shape.len() - 1)
145 .eval(tvec!(data.into_tvalue(), indices.into_tvalue()))?;
146 Ok(outputs.remove(0))
147 }
148
149 #[test]
150 fn last_axis_resolves_negative_indices() {
151 let output = gather(&[1, 4], &[-1, 0, -4, 2]).unwrap();
152 assert_eq!(output.try_as_plain().unwrap().as_slice::<f32>().unwrap(), [3., 0., 0., 2.]);
153 }
154
155 #[test]
156 fn last_axis_rejects_index_past_the_end() {
157 assert!(gather(&[1, 4], &[0, 4]).is_err());
158 }
159
160 #[test]
161 fn last_axis_rejects_index_before_the_start() {
162 assert!(gather(&[1, 4], &[0, -5]).is_err());
163 }
164
165 #[test]
166 fn last_axis_rejects_any_index_into_an_empty_axis() {
167 assert!(gather(&[1, 0], &[0]).is_err());
168 }
169}