use crate::*;
#[derive(Clone, Debug)]
pub(crate) struct ContractShape {
pub space: Space,
pub acc_axes: MatrixAxes,
pub reduce: Vec<Axis>,
pub reduce_extents: Vec<usize>,
pub kc: usize,
pub mr: usize,
pub nr: usize,
pub cols: usize,
pub contracted_per_step: usize,
pub spread: usize,
pub lw: usize,
pub vw: usize,
pub aw: usize,
}
impl ContractShape {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
lhs: &Space,
rhs: &Space,
space: Space,
contracted_per_step: usize,
lw: usize,
vw: usize,
aw: usize,
) -> Self {
let merged = Space::merge(&[lhs, rhs]);
let acc_axes = MatrixAxes::accumulator(&space, lhs);
let reduce = Space::contracted(&[lhs, rhs], &space).to_vec();
let reduce_extents = reduce
.iter()
.map(|&axis| merged.extent(axis))
.collect::<Vec<_>>();
let cols = acc_axes.cols(&space);
let spread = if contracted_per_step > 1 { 1 } else { vw / aw };
let cell = column_cell_width(contracted_per_step, spread, vw);
let nr = if spread > 1 {
cols.div_ceil(cell)
} else {
cols / cell
};
let mr = acc_axes.rows(&space);
Self {
kc: reduce_extents.iter().product(),
mr,
nr,
cols,
space,
acc_axes,
reduce,
reduce_extents,
contracted_per_step,
spread,
lw,
vw,
aw,
}
}
pub(crate) fn matrix_axes(&self, lhs: &Space, rhs: &Space) -> Option<(MatrixAxes, MatrixAxes)> {
let lhs_axes = MatrixAxes::new(lhs, self.mr, self.kc).ok()?;
let rhs_axes = match self.contracted_per_step > 1 {
true => MatrixAxes::new(rhs, self.cols, self.kc).ok()?,
false => MatrixAxes::new(rhs, self.kc, self.cols).ok()?,
};
Some((lhs_axes, rhs_axes))
}
pub(crate) fn lhs_axes(&self, lhs: &Space) -> MatrixAxes {
MatrixAxes::new(lhs, self.mr, self.kc).unwrap_or_else(|e| panic!("{e}"))
}
pub(crate) fn rhs_axes(&self, rhs: &Space) -> MatrixAxes {
match self.contracted_per_step > 1 {
true => MatrixAxes::new(rhs, self.cols, self.kc).unwrap_or_else(|e| panic!("{e}")),
false => MatrixAxes::new(rhs, self.kc, self.cols).unwrap_or_else(|e| panic!("{e}")),
}
}
pub(crate) fn cell_width(&self) -> usize {
column_cell_width(self.contracted_per_step, self.spread, self.vw)
}
pub(crate) fn row_extents(&self) -> Vec<usize> {
line_extents(
&self.space,
1,
self.acc_axes.row_split,
self.acc_axes.col_split,
)
}
pub(crate) fn column_line_extents(&self) -> Vec<usize> {
line_extents(
&self.space,
self.cell_width(),
self.acc_axes.col_split,
self.space.rank(),
)
}
pub(crate) fn batch_extents(&self) -> Vec<usize> {
(0..self.acc_axes.row_split)
.map(|p| self.space.extent_at(p))
.collect()
}
pub(crate) fn matrices(&self) -> usize {
self.batch_extents().iter().product()
}
pub(crate) fn scalars(&self) -> usize {
self.mr * self.nr * self.contracted_per_step * self.aw * self.spread
}
pub(crate) fn component_index_exact(&self) -> bool {
self.reduce.len() == 1
|| self.reduce_extents[self.reduce_extents.len() - 1].is_multiple_of(self.lw)
}
}
fn column_cell_width(contracted_per_step: usize, spread: usize, vw: usize) -> usize {
if contracted_per_step > 1 {
1
} else if spread > 1 {
spread
} else {
vw
}
}
#[cfg(test)]
mod tests {
use super::*;
const B: Axis = Axis(0);
const OH: Axis = Axis(1);
const OW: Axis = Axis(2);
const C: Axis = Axis(3);
const RH: Axis = Axis(4);
const RW: Axis = Axis(5);
#[test]
fn the_cell_coordinate_covers_every_accumulator_axis() {
let acc = Space::new(&[(B, 1), (OH, 1), (OW, 4), (C, 4)]);
let lhs = Space::new(&[(C, 4), (RH, 3), (RW, 3)]);
let rhs = Space::new(&[(B, 1), (OH, 1), (OW, 4), (C, 4), (RH, 3), (RW, 3)]);
let shape = ContractShape::new(&lhs, &rhs, acc.clone(), 1, 4, 4, 4);
assert_eq!(shape.acc_axes.col_split, 1);
assert_eq!(
shape.batch_extents().len()
+ shape.row_extents().len()
+ shape.column_line_extents().len(),
acc.rank()
);
assert_eq!(shape.row_extents().iter().product::<usize>(), shape.mr);
assert_eq!(
shape.column_line_extents().iter().product::<usize>(),
shape.nr
);
}
#[test]
fn a_single_axis_per_edge_leaves_the_coordinate_unchanged() {
let acc = Space::new(&[(B, 2), (OH, 4), (C, 8)]);
let lhs = Space::new(&[(B, 2), (OH, 4), (RH, 6)]);
let rhs = Space::new(&[(B, 2), (RH, 6), (C, 8)]);
let shape = ContractShape::new(&lhs, &rhs, acc.clone(), 1, 4, 4, 4);
assert_eq!(shape.batch_extents(), vec![2]);
assert_eq!(shape.row_extents(), vec![4]);
assert_eq!(shape.column_line_extents(), vec![2]);
}
}