1use crate::{DimVec, Error, Result, Shape, StrideVec};
8use smallvec::smallvec;
9
10#[derive(Debug, PartialEq, Eq, Clone)]
25pub struct Layout {
26 shape: Shape,
27 stride: StrideVec,
29 start_offset: usize,
30}
31
32impl Layout {
33 pub fn new(shape: Shape, stride: StrideVec, start_offset: usize) -> Self {
35 Self {
36 shape,
37 stride,
38 start_offset,
39 }
40 }
41
42 pub fn contiguous_with_offset<S: Into<Shape>>(shape: S, start_offset: usize) -> Self {
44 let shape = shape.into();
45 let stride = shape.stride_contiguous();
46 Self {
47 shape,
48 stride,
49 start_offset,
50 }
51 }
52
53 pub fn contiguous<S: Into<Shape>>(shape: S) -> Self {
55 Self::contiguous_with_offset(shape, 0)
56 }
57
58 pub fn dims(&self) -> &[usize] {
60 self.shape.dims()
61 }
62
63 pub fn dim<D: crate::shape::Dim>(&self, dim: D) -> Result<usize> {
65 let dim = dim.to_index(&self.shape, "dim")?;
66 Ok(self.dims()[dim])
67 }
68
69 pub fn shape(&self) -> &Shape {
71 &self.shape
72 }
73
74 pub fn has_dynamic(&self) -> bool {
77 self.shape.has_dynamic()
78 }
79
80 pub fn resolve(&self, env: &crate::SymEnv) -> Result<Layout> {
86 Ok(Layout {
87 shape: self.shape.resolve(env)?,
88 stride: self.stride.clone(),
89 start_offset: self.start_offset,
90 })
91 }
92
93 pub fn stride(&self) -> &[isize] {
96 &self.stride
97 }
98
99 pub fn stride_unsigned(&self) -> DimVec {
109 self.stride
110 .iter()
111 .map(|&s| {
112 debug_assert!(
113 s >= 0,
114 "stride_unsigned() called on a layout with negative stride; \
115 use StridedIndex or operate on signed strides"
116 );
117 s as usize
118 })
119 .collect()
120 }
121
122 pub fn start_offset(&self) -> usize {
124 self.start_offset
125 }
126
127 pub fn contiguous_offsets(&self) -> Option<(usize, usize)> {
129 if self.is_contiguous() {
130 let start_o = self.start_offset;
131 Some((start_o, start_o + self.shape.elem_count()))
132 } else {
133 None
134 }
135 }
136
137 pub fn is_contiguous(&self) -> bool {
141 self.shape.is_contiguous(&self.stride)
142 }
143
144 pub fn is_fortran_contiguous(&self) -> bool {
146 self.shape.is_fortran_contiguous(&self.stride)
147 }
148
149 pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Result<Self> {
151 let dims = self.shape().dims();
152 if dim >= dims.len() {
153 Err(Error::DimOutOfRange {
154 shape: Box::new(self.shape().clone()),
155 dim: dim as i32,
156 op: "narrow",
157 }
158 .bt())?
159 }
160 if start + len > dims[dim] {
161 Err(Error::NarrowInvalidArgs {
162 shape: Box::new(self.shape.clone()),
163 dim,
164 start,
165 len,
166 msg: "start + len > dim_len",
167 }
168 .bt())?
169 }
170 let mut dims = DimVec::from_slice(dims);
171 dims[dim] = len;
172 let new_start = (self.start_offset as isize + self.stride[dim] * start as isize) as usize;
182 Ok(Self {
183 shape: Shape::from(dims),
184 stride: self.stride.clone(),
185 start_offset: new_start,
186 })
187 }
188
189 pub fn transpose(&self, dim1: usize, dim2: usize) -> Result<Self> {
191 let rank = self.shape.rank();
192 if rank <= dim1 || rank <= dim2 {
193 Err(Error::UnexpectedNumberOfDims {
194 expected: usize::max(dim1, dim2),
195 got: rank,
196 shape: Box::new(self.shape().clone()),
197 }
198 .bt())?
199 }
200 let mut stride = StrideVec::from_slice(self.stride());
201 let mut dims = DimVec::from_slice(self.shape().dims());
202 dims.swap(dim1, dim2);
203 stride.swap(dim1, dim2);
204 Ok(Self {
205 shape: Shape::from(dims),
206 stride,
207 start_offset: self.start_offset,
208 })
209 }
210
211 pub fn permute(&self, idxs: &[usize]) -> Result<Self> {
213 let is_permutation =
214 idxs.len() == self.shape.rank() && (0..idxs.len()).all(|i| idxs.contains(&i));
215 if !is_permutation {
216 crate::bail!(
217 "dimension mismatch in permute, tensor {:?}, dims: {:?}",
218 self.dims(),
219 idxs
220 )
221 }
222 let stride = self.stride();
223 let dims = self.shape().dims();
224 let mut perm_stride = StrideVec::from_slice(stride);
225 let mut perm_dims = DimVec::from_slice(dims);
226 for (i, &idx) in idxs.iter().enumerate() {
227 perm_stride[i] = stride[idx];
228 perm_dims[i] = dims[idx];
229 }
230 Ok(Self {
231 shape: Shape::from(perm_dims),
232 stride: perm_stride,
233 start_offset: self.start_offset,
234 })
235 }
236
237 pub fn unsqueeze(&self, dim: usize) -> Result<Self> {
249 let rank = self.shape().rank();
250 if dim > rank {
251 return Err(Error::Msg(format!(
252 "unsqueeze: dim {dim} out of bounds for rank {rank}",
253 ))
254 .bt());
255 }
256 let mut new_dims = DimVec::from_slice(self.shape().dims());
257 new_dims.insert(dim, 1);
258 let mut new_stride = StrideVec::from_slice(self.stride());
259 new_stride.insert(dim, 0);
260 Ok(Self {
261 shape: Shape::from(new_dims),
262 stride: new_stride,
263 start_offset: self.start_offset,
264 })
265 }
266
267 pub fn squeeze(&self, dim: usize) -> Result<Self> {
272 let rank = self.shape().rank();
273 if dim >= rank {
274 return Err(
275 Error::Msg(format!("squeeze: dim {dim} out of bounds for rank {rank}",)).bt(),
276 );
277 }
278 if self.shape().dims()[dim] != 1 {
279 return Err(Error::Msg(format!(
280 "squeeze: dim {dim} has size {}, expected 1",
281 self.shape().dims()[dim],
282 ))
283 .bt());
284 }
285 let mut new_dims = DimVec::from_slice(self.shape().dims());
286 new_dims.remove(dim);
287 let mut new_stride = StrideVec::from_slice(self.stride());
288 new_stride.remove(dim);
289 Ok(Self {
290 shape: Shape::from(new_dims),
291 stride: new_stride,
292 start_offset: self.start_offset,
293 })
294 }
295
296 pub fn flip(&self, dim: usize) -> Result<Self> {
306 let rank = self.shape().rank();
307 if dim >= rank {
308 return Err(Error::Msg(format!("flip: dim {dim} out of bounds for rank {rank}",)).bt());
309 }
310 let size = self.shape().dims()[dim];
311 let mut new_stride = StrideVec::from_slice(self.stride());
312 let s = new_stride[dim];
313 new_stride[dim] = -s;
314 let shift = if size > 1 { (size as isize - 1) * s } else { 0 };
318 let new_start = (self.start_offset as isize + shift) as usize;
323 Ok(Self {
324 shape: self.shape.clone(),
325 stride: new_stride,
326 start_offset: new_start,
327 })
328 }
329
330 pub fn broadcast_as<S: Into<Shape>>(&self, shape: S) -> Result<Self> {
334 let shape = shape.into();
335 if shape.rank() < self.shape().rank() {
336 return Err(Error::BroadcastIncompatibleShapes {
337 src_shape: Box::new(self.shape().clone()),
338 dst_shape: Box::new(shape),
339 }
340 .bt());
341 }
342 let added_dims = shape.rank() - self.shape().rank();
343 let mut stride: StrideVec = smallvec![0_isize; added_dims];
344 for (&dst_dim, (&src_dim, &src_stride)) in shape.dims()[added_dims..]
345 .iter()
346 .zip(self.dims().iter().zip(self.stride()))
347 {
348 let s = if dst_dim == src_dim {
349 src_stride
350 } else if src_dim != 1 {
351 return Err(Error::BroadcastIncompatibleShapes {
352 src_shape: Box::new(self.shape().clone()),
353 dst_shape: Box::new(shape),
354 }
355 .bt());
356 } else {
357 0
358 };
359 stride.push(s)
360 }
361 Ok(Self {
362 shape,
363 stride,
364 start_offset: self.start_offset,
365 })
366 }
367
368 pub fn strided_index(&self) -> crate::StridedIndex<'_> {
369 crate::StridedIndex::from_layout(self)
370 }
371
372 pub fn strided_blocks(&self) -> crate::StridedBlocks<'_> {
373 let mut block_len: usize = 1;
374 let mut contiguous_dims = 0; for (&stride, &dim) in self.stride().iter().zip(self.dims().iter()).rev() {
376 if stride != block_len as isize {
380 break;
381 }
382 block_len *= dim;
383 contiguous_dims += 1;
384 }
385 let index_dims = self.dims().len() - contiguous_dims;
386 if index_dims == 0 {
387 crate::StridedBlocks::SingleBlock {
388 start_offset: self.start_offset,
389 len: block_len,
390 }
391 } else {
392 let block_start_index = crate::StridedIndex::new(
393 &self.dims()[..index_dims],
394 &self.stride[..index_dims],
395 self.start_offset,
396 );
397 crate::StridedBlocks::MultipleBlocks {
398 block_start_index,
399 block_len,
400 }
401 }
402 }
403
404 pub fn offsets_b(&self) -> Option<ContiguousOffsetsWithBroadcast> {
406 let mut left_broadcast: usize = 1;
407 let mut right_broadcast: usize = 1;
408 let strides = self.stride();
409 let dims = self.dims();
410 let mut start_cont = 0;
411 let mut end_cont = dims.len();
412 for (&s, &d) in strides.iter().zip(dims.iter()) {
413 if s != 0 {
414 break;
415 }
416 start_cont += 1;
417 left_broadcast *= d;
418 }
419 if start_cont == dims.len() {
420 return Some(ContiguousOffsetsWithBroadcast {
421 start: self.start_offset,
422 len: 1,
423 left_broadcast,
424 right_broadcast: 1,
425 });
426 }
427 for (&s, &d) in strides.iter().zip(dims.iter()).rev() {
428 if s != 0 {
429 break;
430 }
431 end_cont -= 1;
432 right_broadcast *= d;
433 }
434 let strides = &strides[start_cont..end_cont];
436 let dims = &dims[start_cont..end_cont];
437 let mut len: usize = 1;
438 for (&stride, &dim) in strides.iter().zip(dims.iter()).rev() {
439 if stride != len as isize {
441 return None;
442 }
443 len *= dim;
444 }
445 Some(ContiguousOffsetsWithBroadcast {
446 start: self.start_offset,
447 len,
448 left_broadcast,
449 right_broadcast,
450 })
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn flip_dim0_of_3x4_negates_stride_and_shifts_offset() {
460 let l = Layout::contiguous(Shape::from_dims(&[3, 4]));
461 let f = l.flip(0).unwrap();
462 assert_eq!(f.shape().dims(), &[3, 4]);
463 assert_eq!(f.stride(), &[-4_isize, 1]);
464 assert_eq!(f.start_offset(), 8); assert!(!f.is_contiguous());
466 }
467
468 #[test]
469 fn flip_is_involutive() {
470 let l = Layout::contiguous(Shape::from_dims(&[3, 4]));
471 let ff = l.flip(0).unwrap().flip(0).unwrap();
472 assert_eq!(ff.shape().dims(), l.shape().dims());
473 assert_eq!(ff.stride(), l.stride());
474 assert_eq!(ff.start_offset(), l.start_offset());
475 }
476
477 #[test]
478 fn flip_size_one_axis_is_noop_offset() {
479 let l = Layout::contiguous(Shape::from_dims(&[1, 4]));
480 let f = l.flip(0).unwrap();
481 assert_eq!(f.start_offset(), 0);
482 assert_eq!(f.stride(), &[-4_isize, 1]);
483 }
484
485 #[test]
486 fn flip_iteration_yields_reversed_offsets() {
487 let l = Layout::contiguous(Shape::from_dims(&[4]));
488 let f = l.flip(0).unwrap();
489 let offsets: Vec<usize> = f.strided_index().collect();
490 assert_eq!(offsets, vec![3, 2, 1, 0]);
491 }
492
493 #[test]
494 fn flip_dim_oob_errors() {
495 let l = Layout::contiguous(Shape::from_dims(&[3, 4]));
496 assert!(l.flip(2).is_err());
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct ContiguousOffsetsWithBroadcast {
504 pub start: usize,
506 pub len: usize,
508 pub left_broadcast: usize,
510 pub right_broadcast: usize,
512}