Implement HStackLazy and VStackLazy, allowing for the removal of intermediate allocations when building a matrix with a mix of horizontal and vertical stacking.

This commit is contained in:
Avi Weinstock 2023-02-05 16:58:59 -05:00
parent aec3ae2d53
commit 90010b1a50
3 changed files with 243 additions and 151 deletions

View File

@ -308,7 +308,7 @@ fn dvector_arbitrary_expressions() {
#[test] #[test]
fn test_stacking() { fn test_stacking() {
use nalgebra::{hstack, vstack, RowVector3, RowVector4}; use nalgebra::{hstack, vstack, Const, HStackLazy, RowVector3, RowVector4, VStackLazy};
assert_eq_and_type!( assert_eq_and_type!(
vstack((&RowVector3::new(1, 2, 3), &RowVector3::new(4, 5, 6))), vstack((&RowVector3::new(1, 2, 3), &RowVector3::new(4, 5, 6))),
Matrix2x3::new(1, 2, 3, 4, 5, 6) Matrix2x3::new(1, 2, 3, 4, 5, 6)
@ -347,4 +347,22 @@ fn test_stacking() {
)), )),
dmatrix![1, 0, 0, 2; 0, 1, 0, 2; 0, 0, 1, 2; 3, 3, 3, 3] dmatrix![1, 0, 0, 2; 0, 1, 0, 2; 0, 0, 1, 2; 3, 3, 3, 3]
); );
assert_eq_and_type!(
hstack::<_, _, Const<4>, _>((&Matrix2::<usize>::identity(), &nalgebra::SMatrix::zeros())),
matrix![1, 0, 0, 0; 0, 1, 0, 0]
);
assert_eq_and_type!(
vstack((
HStackLazy((&Matrix2::<usize>::identity(), &Matrix2::zeros())),
HStackLazy((&Matrix2::identity(), &Matrix2::identity()))
)),
matrix![1, 0, 0, 0; 0, 1, 0, 0; 1, 0, 1, 0; 0, 1, 0, 1]
);
assert_eq_and_type!(
hstack((
VStackLazy((&Matrix2::<usize>::identity(), &Matrix2::identity())),
VStackLazy((&Matrix2::zeros(), &Matrix2::identity()))
)),
matrix![1, 0, 0, 0; 0, 1, 0, 0; 1, 0, 1, 0; 0, 1, 0, 1]
);
} }

View File

@ -62,7 +62,7 @@ pub use self::alias_slice::*;
pub use self::alias_view::*; pub use self::alias_view::*;
pub use self::array_storage::*; pub use self::array_storage::*;
pub use self::matrix_view::*; pub use self::matrix_view::*;
pub use self::stacking::{hstack, vstack}; pub use self::stacking::*;
pub use self::storage::*; pub use self::storage::*;
#[cfg(any(feature = "std", feature = "alloc"))] #[cfg(any(feature = "std", feature = "alloc"))]
pub use self::vec_storage::*; pub use self::vec_storage::*;

View File

@ -2,9 +2,11 @@
use crate::{ use crate::{
base::allocator::Allocator, base::allocator::Allocator,
constraint::{DimEq, SameNumberOfColumns, SameNumberOfRows, ShapeConstraint}, constraint::{DimEq, SameNumberOfColumns, SameNumberOfRows, ShapeConstraint},
Const, DefaultAllocator, Dim, DimAdd, DimSum, Dyn, Matrix, RawStorage, RawStorageMut, Scalar, Const, DefaultAllocator, Dim, DimAdd, DimDiff, DimSub, DimSum, Matrix, RawStorage,
RawStorageMut, Scalar, ViewStorageMut,
}; };
use num_traits::Zero; use num_traits::Zero;
use std::marker::PhantomData;
/// A visitor for each folding over each element of a tuple. /// A visitor for each folding over each element of a tuple.
pub trait Visitor<A> { pub trait Visitor<A> {
@ -59,42 +61,136 @@ macro_rules! impl_visit_tuple {
impl_visit_tuple!(H, G, F, E, D, C, B, A); impl_visit_tuple!(H, G, F, E, D, C, B, A);
/// Source of data that can populate a block of a matrix.
pub trait Block {
/// The scalar type of the data.
type T: Scalar;
/// The number of rows of the block.
type Rows: Dim;
/// The number of columns of the block.
type Cols: Dim;
/// The shape of the block.
fn shape(self) -> (Self::Rows, Self::Cols);
}
/// Source of data that can populate a block of a matrix.
/// Separate from Block because it's useful to specify the bound on the storage independently of
/// the other bounds.
pub trait BlockPopulate<S>: Block
where
S: RawStorageMut<Self::T, Self::Rows, Self::Cols>,
{
/// Populate a matrix from this block's data.
fn populate(self, m: &mut Matrix<Self::T, Self::Rows, Self::Cols, S>);
}
impl<'a, T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Block for &'a Matrix<T, R, C, S> {
type T = T;
type Rows = R;
type Cols = C;
#[inline(always)]
fn shape(self) -> (Self::Rows, Self::Cols) {
self.shape_generic()
}
}
impl<'a, T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>, S2: RawStorageMut<T, R, C>>
BlockPopulate<S2> for &'a Matrix<T, R, C, S>
{
#[inline(always)]
fn populate(self, m: &mut Matrix<T, Self::Rows, Self::Cols, S2>) {
m.copy_from(self);
}
}
#[inline]
fn build<B: Copy, T: Scalar + Zero, R: Dim, C: Dim, S>(x: B) -> Matrix<T, R, C, S>
where
S: RawStorageMut<T, R, C>,
DefaultAllocator: Allocator<T, R, C, Buffer = S>,
B: Block<T = T, Rows = R, Cols = C> + BlockPopulate<S>,
{
let (r, c) = x.shape();
let mut out = Matrix::zeros_generic(r, c);
x.populate(&mut out);
out
}
mod vstack_impl { mod vstack_impl {
use super::*; use super::*;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct VStackShapeInit; pub struct VStackShapeInit;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct VStackShape<R, C> { pub struct VStackShape<T, R, C> {
t: PhantomData<T>,
r: R, r: R,
c: C, c: C,
} }
impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Visitor<&Matrix<T, R, C, S>> impl<B: Block> Visitor<B> for VStackShapeInit {
for VStackShapeInit type Output = VStackShape<B::T, B::Rows, B::Cols>;
{ fn visit(self, x: B) -> Self::Output {
type Output = VStackShape<R, C>; let (r, c) = x.shape();
fn visit(self, x: &Matrix<T, R, C, S>) -> Self::Output { VStackShape {
let (r, c) = x.shape_generic(); t: PhantomData,
VStackShape { r, c } r,
c,
}
} }
} }
impl<T: Scalar, R1: Dim + DimAdd<R2>, C1: Dim, R2: Dim, C2: Dim, S2: RawStorage<T, R2, C2>> impl<B: Block, R1: Dim + DimAdd<B::Rows>, C1: Dim> Visitor<B> for VStackShape<B::T, R1, C1>
Visitor<&Matrix<T, R2, C2, S2>> for VStackShape<R1, C1>
where where
ShapeConstraint: SameNumberOfColumns<C1, C2>, DimSum<R1, B::Rows>: DimSub<R1> + DimSub<B::Rows>,
ShapeConstraint: SameNumberOfColumns<C1, B::Cols>
+ SameNumberOfRows<DimDiff<DimSum<R1, B::Rows>, R1>, B::Rows>
+ SameNumberOfRows<DimDiff<DimSum<R1, B::Rows>, B::Rows>, R1>,
{ {
type Output = type Output = VStackShape<
VStackShape<DimSum<R1, R2>, <ShapeConstraint as DimEq<C1, C2>>::Representative>; B::T,
fn visit(self, x: &Matrix<T, R2, C2, S2>) -> Self::Output { DimSum<R1, B::Rows>,
let (r, c) = x.shape_generic(); <ShapeConstraint as DimEq<C1, B::Cols>>::Representative,
>;
fn visit(self, x: B) -> Self::Output {
let (r, c) = x.shape();
VStackShape { VStackShape {
t: self.t,
r: self.r.add(r), r: self.r.add(r),
c: <ShapeConstraint as DimEq<C1, C2>>::Representative::from_usize(c.value()), c: <ShapeConstraint as DimEq<C1, B::Cols>>::Representative::from_usize(c.value()),
} }
} }
} }
/// Specify vertical stacking as a Block.
#[derive(Copy, Clone)]
pub struct VStackLazy<X>(pub X);
impl<T: Scalar, R: Dim, C: Dim, X> Block for VStackLazy<X>
where
X: Copy + VisitTuple<VStackShapeInit, Output = VStackShape<T, R, C>>,
{
type T = T;
type Rows = R;
type Cols = C;
fn shape(self) -> (Self::Rows, Self::Cols) {
let shape = <X as VisitTuple<_>>::visit(VStackShapeInit, self.0);
(shape.r, shape.c)
}
}
impl<T: Scalar, R: Dim, C: Dim, S: RawStorageMut<T, R, C>, X> BlockPopulate<S> for VStackLazy<X>
where
X: Copy
+ VisitTuple<VStackShapeInit, Output = VStackShape<T, R, C>>
+ for<'a> VisitTuple<VStack<'a, T, R, C, S, Const<0>>, Output = VStack<'a, T, R, C, S, R>>,
{
fn populate(self, m: &mut Matrix<T, R, C, S>) {
let vstack_visitor = VStack {
out: m,
current_row: Const,
};
let _ = <X as VisitTuple<_>>::visit(vstack_visitor, self.0);
}
}
pub struct VStack<'a, T, R, C, S, R2> { pub struct VStack<'a, T, R, C, S, R2> {
out: &'a mut Matrix<T, R, C, S>, out: &'a mut Matrix<T, R, C, S>,
current_row: R2, current_row: R2,
@ -102,47 +198,32 @@ mod vstack_impl {
impl< impl<
'a, 'a,
T: Scalar, B: Copy
R1: Dim + DimAdd<Const<R2>>, + Block
+ for<'b> BlockPopulate<
ViewStorageMut<
'b,
<B as Block>::T,
<B as Block>::Rows,
<B as Block>::Cols,
<S1 as RawStorage<<B as Block>::T, R1, C1>>::RStride,
<S1 as RawStorage<<B as Block>::T, R1, C1>>::CStride,
>,
>,
R1: Dim + DimAdd<B::Rows>,
C1: Dim, C1: Dim,
S1: RawStorageMut<T, R1, C1>, S1: RawStorageMut<B::T, R1, C1>,
C2: Dim, R3: Dim + DimAdd<B::Rows>,
S2: RawStorage<T, Const<R2>, C2>, > Visitor<B> for VStack<'a, B::T, R1, C1, S1, R3>
R3: Dim + DimAdd<Const<R2>>,
const R2: usize,
> Visitor<&Matrix<T, Const<R2>, C2, S2>> for VStack<'a, T, R1, C1, S1, R3>
where where
ShapeConstraint: SameNumberOfColumns<C1, C2>, B::T: Scalar,
ShapeConstraint: SameNumberOfColumns<C1, B::Cols>,
{ {
type Output = VStack<'a, T, R1, C1, S1, DimSum<R3, Const<R2>>>; type Output = VStack<'a, B::T, R1, C1, S1, DimSum<R3, B::Rows>>;
fn visit(self, x: &Matrix<T, Const<R2>, C2, S2>) -> Self::Output { fn visit(self, x: B) -> Self::Output {
let (r2, _) = x.shape_generic(); let (r2, c2) = x.shape();
let VStack { out, current_row } = self; let VStack { out, current_row } = self;
out.fixed_rows_mut::<{ R2 }>(current_row.value()) x.populate(&mut out.generic_view_mut((current_row.value(), 0), (r2, c2)));
.copy_from::<Const<R2>, C2, S2>(x);
let current_row = current_row.add(r2);
VStack { out, current_row }
}
}
impl<
'a,
T: Scalar,
R1: Dim + DimAdd<Dyn>,
C1: Dim,
S1: RawStorageMut<T, R1, C1>,
C2: Dim,
S2: RawStorage<T, Dyn, C2>,
R3: Dim + DimAdd<Dyn>,
> Visitor<&Matrix<T, Dyn, C2, S2>> for VStack<'a, T, R1, C1, S1, R3>
where
ShapeConstraint: SameNumberOfColumns<C1, C2>,
{
type Output = VStack<'a, T, R1, C1, S1, DimSum<R3, Dyn>>;
fn visit(self, x: &Matrix<T, Dyn, C2, S2>) -> Self::Output {
let (r2, _) = x.shape_generic();
let VStack { out, current_row } = self;
out.rows_mut(current_row.value(), r2.value())
.copy_from::<Dyn, C2, S2>(x);
let current_row = current_row.add(r2); let current_row = current_row.add(r2);
VStack { out, current_row } VStack { out, current_row }
} }
@ -151,34 +232,18 @@ mod vstack_impl {
/// Stack a tuple of references to matrices with equal column counts vertically, yielding a /// Stack a tuple of references to matrices with equal column counts vertically, yielding a
/// matrix with every row of the input matrices. /// matrix with every row of the input matrices.
#[inline] #[inline]
pub fn vstack< pub fn vstack<T: Scalar + Zero, R: Dim, C: Dim, X: Copy>(
T: Scalar + Zero,
R: Dim,
C: Dim,
X: Copy
+ VisitTuple<VStackShapeInit, Output = VStackShape<R, C>>
+ for<'a> VisitTuple<
VStack<'a, T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer, Const<0>>,
Output = VStack<'a, T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer, R>,
>,
>(
x: X, x: X,
) -> Matrix<T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer> ) -> Matrix<T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer>
where where
DefaultAllocator: Allocator<T, R, C>, DefaultAllocator: Allocator<T, R, C>,
VStackLazy<X>: Block<T = T, Rows = R, Cols = C>
+ BlockPopulate<<DefaultAllocator as Allocator<T, R, C>>::Buffer>,
{ {
let vstack_shape = VStackShapeInit; build(VStackLazy(x))
let vstack_shape = <X as VisitTuple<_>>::visit(vstack_shape, x);
let mut out = Matrix::zeros_generic(vstack_shape.r, vstack_shape.c);
let vstack_visitor = VStack {
out: &mut out,
current_row: Const,
};
let _ = <X as VisitTuple<_>>::visit(vstack_visitor, x);
out
} }
} }
pub use vstack_impl::vstack; pub use vstack_impl::{vstack, VStackLazy};
mod hstack_impl { mod hstack_impl {
use super::*; use super::*;
@ -186,36 +251,76 @@ mod hstack_impl {
pub struct HStackShapeInit; pub struct HStackShapeInit;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct HStackShape<R, C> { pub struct HStackShape<T, R, C> {
t: PhantomData<T>,
r: R, r: R,
c: C, c: C,
} }
impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Visitor<&Matrix<T, R, C, S>> impl<B: Block> Visitor<B> for HStackShapeInit {
for HStackShapeInit type Output = HStackShape<B::T, B::Rows, B::Cols>;
{ fn visit(self, x: B) -> Self::Output {
type Output = HStackShape<R, C>; let (r, c) = x.shape();
fn visit(self, x: &Matrix<T, R, C, S>) -> Self::Output { HStackShape {
let (r, c) = x.shape_generic(); t: PhantomData,
HStackShape { r, c } r,
c,
}
} }
} }
impl<T: Scalar, R1: Dim, C1: Dim + DimAdd<C2>, R2: Dim, C2: Dim, S2: RawStorage<T, R2, C2>> impl<B: Block, R1: Dim, C1: Dim + DimAdd<B::Cols>> Visitor<B> for HStackShape<B::T, R1, C1>
Visitor<&Matrix<T, R2, C2, S2>> for HStackShape<R1, C1>
where where
ShapeConstraint: SameNumberOfRows<R1, R2>, DimSum<C1, B::Cols>: DimSub<C1> + DimSub<B::Cols>,
ShapeConstraint: SameNumberOfRows<R1, B::Rows>
+ SameNumberOfColumns<DimDiff<DimSum<C1, B::Cols>, C1>, B::Cols>
+ SameNumberOfColumns<DimDiff<DimSum<C1, B::Cols>, B::Cols>, C1>,
{ {
type Output = type Output = HStackShape<
HStackShape<<ShapeConstraint as DimEq<R1, R2>>::Representative, DimSum<C1, C2>>; B::T,
fn visit(self, x: &Matrix<T, R2, C2, S2>) -> Self::Output { <ShapeConstraint as DimEq<R1, B::Rows>>::Representative,
let (r, c) = x.shape_generic(); DimSum<C1, B::Cols>,
>;
fn visit(self, x: B) -> Self::Output {
let (r, c) = x.shape();
HStackShape { HStackShape {
r: <ShapeConstraint as DimEq<R1, R2>>::Representative::from_usize(r.value()), t: self.t,
r: <ShapeConstraint as DimEq<R1, B::Rows>>::Representative::from_usize(r.value()),
c: self.c.add(c), c: self.c.add(c),
} }
} }
} }
/// Specify horizontal stacking as a Block.
#[derive(Copy, Clone)]
pub struct HStackLazy<X>(pub X);
impl<T: Scalar, R: Dim, C: Dim, X> Block for HStackLazy<X>
where
X: Copy + VisitTuple<HStackShapeInit, Output = HStackShape<T, R, C>>,
{
type T = T;
type Rows = R;
type Cols = C;
fn shape(self) -> (Self::Rows, Self::Cols) {
let shape = <X as VisitTuple<_>>::visit(HStackShapeInit, self.0);
(shape.r, shape.c)
}
}
impl<T: Scalar, R: Dim, C: Dim, S: RawStorageMut<T, R, C>, X> BlockPopulate<S> for HStackLazy<X>
where
X: Copy
+ VisitTuple<HStackShapeInit, Output = HStackShape<T, R, C>>
+ for<'a> VisitTuple<HStack<'a, T, R, C, S, Const<0>>, Output = HStack<'a, T, R, C, S, C>>,
{
fn populate(self, m: &mut Matrix<T, R, C, S>) {
let hstack_visitor = HStack {
out: m,
current_col: Const,
};
let _ = <X as VisitTuple<_>>::visit(hstack_visitor, self.0);
}
}
pub struct HStack<'a, T, R, C, S, C2> { pub struct HStack<'a, T, R, C, S, C2> {
out: &'a mut Matrix<T, R, C, S>, out: &'a mut Matrix<T, R, C, S>,
current_col: C2, current_col: C2,
@ -223,47 +328,32 @@ mod hstack_impl {
impl< impl<
'a, 'a,
T: Scalar, B: Copy
+ Block
+ for<'b> BlockPopulate<
ViewStorageMut<
'b,
<B as Block>::T,
<B as Block>::Rows,
<B as Block>::Cols,
<S1 as RawStorage<<B as Block>::T, R1, C1>>::RStride,
<S1 as RawStorage<<B as Block>::T, R1, C1>>::CStride,
>,
>,
R1: Dim, R1: Dim,
C1: Dim + DimAdd<Const<C2>>, C1: Dim,
S1: RawStorageMut<T, R1, C1>, S1: RawStorageMut<B::T, R1, C1>,
R2: Dim, C3: Dim + DimAdd<B::Cols>,
S2: RawStorage<T, R2, Const<C2>>, > Visitor<B> for HStack<'a, B::T, R1, C1, S1, C3>
C3: Dim + DimAdd<Const<C2>>,
const C2: usize,
> Visitor<&Matrix<T, R2, Const<C2>, S2>> for HStack<'a, T, R1, C1, S1, C3>
where where
ShapeConstraint: SameNumberOfRows<R1, R2>, B::T: Scalar,
ShapeConstraint: SameNumberOfRows<R1, B::Rows>,
{ {
type Output = HStack<'a, T, R1, C1, S1, DimSum<C3, Const<C2>>>; type Output = HStack<'a, B::T, R1, C1, S1, DimSum<C3, B::Cols>>;
fn visit(self, x: &Matrix<T, R2, Const<C2>, S2>) -> Self::Output { fn visit(self, x: B) -> Self::Output {
let (_, c2) = x.shape_generic(); let (r2, c2) = x.shape();
let HStack { out, current_col } = self; let HStack { out, current_col } = self;
out.fixed_columns_mut::<{ C2 }>(current_col.value()) x.populate(&mut out.generic_view_mut((0, current_col.value()), (r2, c2)));
.copy_from::<R2, Const<C2>, S2>(x);
let current_col = current_col.add(c2);
HStack { out, current_col }
}
}
impl<
'a,
T: Scalar,
R1: Dim,
C1: Dim + DimAdd<Dyn>,
S1: RawStorageMut<T, R1, C1>,
R2: Dim,
S2: RawStorage<T, R2, Dyn>,
C3: Dim + DimAdd<Dyn>,
> Visitor<&Matrix<T, R2, Dyn, S2>> for HStack<'a, T, R1, C1, S1, C3>
where
ShapeConstraint: SameNumberOfRows<R1, R2>,
{
type Output = HStack<'a, T, R1, C1, S1, DimSum<C3, Dyn>>;
fn visit(self, x: &Matrix<T, R2, Dyn, S2>) -> Self::Output {
let (_, c2) = x.shape_generic();
let HStack { out, current_col } = self;
out.columns_mut(current_col.value(), c2.value())
.copy_from::<R2, Dyn, S2>(x);
let current_col = current_col.add(c2); let current_col = current_col.add(c2);
HStack { out, current_col } HStack { out, current_col }
} }
@ -272,31 +362,15 @@ mod hstack_impl {
/// Stack a tuple of references to matrices with equal row counts horizontally, yielding a /// Stack a tuple of references to matrices with equal row counts horizontally, yielding a
/// matrix with every column of the input matrices. /// matrix with every column of the input matrices.
#[inline] #[inline]
pub fn hstack< pub fn hstack<T: Scalar + Zero, R: Dim, C: Dim, X: Copy>(
T: Scalar + Zero,
R: Dim,
C: Dim,
X: Copy
+ VisitTuple<HStackShapeInit, Output = HStackShape<R, C>>
+ for<'a> VisitTuple<
HStack<'a, T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer, Const<0>>,
Output = HStack<'a, T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer, C>,
>,
>(
x: X, x: X,
) -> Matrix<T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer> ) -> Matrix<T, R, C, <DefaultAllocator as Allocator<T, R, C>>::Buffer>
where where
DefaultAllocator: Allocator<T, R, C>, DefaultAllocator: Allocator<T, R, C>,
HStackLazy<X>: Block<T = T, Rows = R, Cols = C>
+ BlockPopulate<<DefaultAllocator as Allocator<T, R, C>>::Buffer>,
{ {
let hstack_shape = HStackShapeInit; build(HStackLazy(x))
let hstack_shape = <X as VisitTuple<_>>::visit(hstack_shape, x);
let mut out = Matrix::zeros_generic(hstack_shape.r, hstack_shape.c);
let hstack_visitor = HStack {
out: &mut out,
current_col: Const,
};
let _ = <X as VisitTuple<_>>::visit(hstack_visitor, x);
out
} }
} }
pub use hstack_impl::hstack; pub use hstack_impl::{hstack, HStackLazy};