#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg_attr(feature = "unsafe", allow(unsafe_code))]
#[cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))]
#[cfg(feature = "ndarray")]
#[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))]
mod ndarray_support;
use core::borrow::Borrow;
use core::fmt::{Display, Formatter};
use core::ops::{Deref, Range};
#[cfg(feature = "ndarray")]
#[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))]
pub use ndarray_support::LagMatrixFromArray;
pub mod prelude {
pub use crate::CreateLagMatrix;
#[cfg(feature = "ndarray")]
#[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))]
pub use crate::ndarray_support::LagMatrixFromArray;
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LagMatrix<T> {
data: Vec<T>,
num_rows: usize,
num_cols: usize,
series_length: usize,
series_count: usize,
num_lags: usize,
row_stride: usize,
row_major: bool,
}
impl<T> LagMatrix<T> {
#[inline(always)]
pub const fn num_rows(&self) -> usize {
self.num_rows
}
#[inline(always)]
pub const fn num_cols(&self) -> usize {
self.num_cols
}
#[inline(always)]
pub const fn series_count(&self) -> usize {
self.series_count
}
#[inline(always)]
pub const fn series_length(&self) -> usize {
self.series_length
}
#[inline(always)]
pub const fn num_lags(&self) -> usize {
self.num_lags
}
#[inline(always)]
pub const fn row_stride(&self) -> usize {
self.row_stride
}
#[inline(always)]
pub const fn is_row_major(&self) -> bool {
self.row_major
}
#[inline(always)]
pub const fn is_column_major(&self) -> bool {
!self.row_major
}
pub const fn matrix_layout(&self) -> MatrixLayout {
if self.row_major {
MatrixLayout::RowMajor(self.series_length)
} else {
MatrixLayout::ColumnMajor(self.series_length)
}
}
#[inline(always)]
pub fn into_vec(self) -> Vec<T> {
self.data
}
}
impl<T> From<LagMatrix<T>> for Vec<T> {
#[inline(always)]
fn from(value: LagMatrix<T>) -> Self {
value.data
}
}
impl<T> From<LagMatrix<T>> for Box<[T]> {
#[inline(always)]
fn from(value: LagMatrix<T>) -> Self {
value.data.into_boxed_slice()
}
}
impl<T> Deref for LagMatrix<T> {
type Target = [T];
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> PartialEq<[T]> for LagMatrix<T>
where
T: PartialEq,
{
fn eq(&self, other: &[T]) -> bool {
self.data.iter().eq(other)
}
}
impl<S, T> PartialEq<S> for LagMatrix<T>
where
S: AsRef<[T]>,
T: PartialEq,
{
#[inline(always)]
fn eq(&self, other: &S) -> bool {
self.data.eq(other.as_ref())
}
}
pub trait CreateLagMatrix<T> {
fn lag_matrix<R: IntoIterator<Item = usize>>(
&self,
lags: R,
fill: T,
stride: usize,
) -> Result<LagMatrix<T>, LagError>;
fn lag_matrix_2d<R: IntoIterator<Item = usize>>(
&self,
layout: MatrixLayout,
lags: R,
fill: T,
row_stride: usize,
) -> Result<LagMatrix<T>, LagError>;
}
impl<S, T> CreateLagMatrix<T> for S
where
S: Borrow<[T]>,
T: Copy,
{
#[inline(always)]
fn lag_matrix<R: IntoIterator<Item = usize>>(
&self,
lags: R,
fill: T,
stride: usize,
) -> Result<LagMatrix<T>, LagError> {
lag_matrix(self.borrow(), lags, fill, stride)
}
#[inline(always)]
fn lag_matrix_2d<R: IntoIterator<Item = usize>>(
&self,
layout: MatrixLayout,
lags: R,
fill: T,
row_stride: usize,
) -> Result<LagMatrix<T>, LagError> {
lag_matrix_2d(self.borrow(), layout, lags, fill, row_stride)
}
}
pub fn lag_matrix<T: Copy, R: IntoIterator<Item = usize>>(
data: &[T],
lags: R,
fill: T,
mut stride: usize,
) -> Result<LagMatrix<T>, LagError> {
let lags = Vec::from_iter(lags);
let num_lags = lags.len();
if num_lags == 0 {
return Err(LagError::InvalidLags);
}
if data.is_empty() {
return Err(LagError::EmptyData);
}
let data_rows = data.len();
if num_lags > data_rows {
return Err(LagError::LagExceedsValueCount);
}
if stride == 0 {
stride = data_rows;
}
if stride < data_rows {
return Err(LagError::InvalidStride);
}
let mut lagged = vec![fill; stride * num_lags];
for (row, lag) in lags.into_iter().enumerate() {
let lagged_offset = row * stride + lag;
let lagged_rows = data_rows - lag;
let lagged_end = lagged_offset + lagged_rows;
let src = &data[0..lagged_rows];
lagged[lagged_offset..lagged_end].copy_from_slice(src);
}
let matrix = LagMatrix {
data: lagged,
num_rows: num_lags,
num_cols: data_rows,
series_length: data_rows,
row_stride: stride,
series_count: 1,
num_lags,
row_major: true,
};
Ok(matrix)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum MatrixLayout {
RowMajor(usize),
ColumnMajor(usize),
}
impl MatrixLayout {
pub fn len(&self) -> usize {
match self {
MatrixLayout::RowMajor(len) => *len,
MatrixLayout::ColumnMajor(len) => *len,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub fn lag_matrix_2d<T: Copy, R: IntoIterator<Item = usize>>(
data_matrix: &[T],
layout: MatrixLayout,
lags: R,
fill: T,
mut row_stride: usize,
) -> Result<LagMatrix<T>, LagError> {
let lags = Vec::from_iter(lags);
let num_lags = lags.len();
if num_lags == 0 {
return Err(LagError::InvalidLags);
}
if data_matrix.is_empty() {
return Err(LagError::EmptyData);
}
let series_length = layout.len();
if num_lags > series_length {
return Err(LagError::LagExceedsValueCount);
}
let num_series = data_matrix.len() / series_length;
if num_series * series_length != data_matrix.len() {
return Err(LagError::InvalidLength);
}
if row_stride == 0 {
row_stride = num_series * lags.len();
}
Ok(match layout {
MatrixLayout::RowMajor(_) => {
if row_stride < series_length {
return Err(LagError::InvalidStride);
}
let mut lagged = vec![fill; num_series * row_stride * num_lags];
for (set, lag) in lags.into_iter().enumerate() {
let set_offset = set * num_series * row_stride;
for s in 0..num_series {
let data_start = s * series_length;
let data_end = (s + 1) * series_length - lag;
let lagged_offset = set_offset + s * row_stride + lag;
let lagged_rows = series_length - lag;
let lagged_end = lagged_offset + lagged_rows;
copy_range(
data_matrix,
&mut lagged,
data_start..data_end,
lagged_offset..lagged_end,
);
}
}
LagMatrix {
data: lagged,
num_rows: num_series * num_lags,
num_cols: series_length,
series_length,
series_count: num_series,
num_lags,
row_stride,
row_major: true,
}
}
MatrixLayout::ColumnMajor(_) => {
if row_stride < num_series * num_lags {
return Err(LagError::InvalidStride);
}
let mut lagged = vec![fill; row_stride * series_length];
for (set, lag) in lags.into_iter().enumerate() {
let set_offset = set * num_series;
for s in 0..(series_length - lag) {
let data_start = s * num_series;
let data_end = (s + 1) * num_series;
let lagged_offset = set_offset + (s + lag) * row_stride;
let lagged_end = lagged_offset + num_series;
copy_range(
data_matrix,
&mut lagged,
data_start..data_end,
lagged_offset..lagged_end,
);
}
}
LagMatrix {
data: lagged,
num_cols: num_series * num_lags,
num_rows: series_length,
series_length,
series_count: num_series,
num_lags,
row_stride,
row_major: false,
}
}
})
}
fn copy_range<T: Copy>(src: &[T], dst: &mut [T], src_range: Range<usize>, dst_range: Range<usize>) {
if cfg!(feature = "unsafe") {
unsafe {
let src: &[T] = src.get_unchecked(src_range);
let dst: &mut [T] = dst.get_unchecked_mut(dst_range);
dst.copy_from_slice(src);
}
} else {
let src: &[T] = &src[src_range];
let dst: &mut [T] = &mut dst[dst_range];
dst.copy_from_slice(src);
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LagError {
InvalidLags,
EmptyData,
LagExceedsValueCount,
InvalidStride,
InvalidLength,
InvalidMemoryLayout,
}
impl std::error::Error for LagError {}
impl Display for LagError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LagError::LagExceedsValueCount => {
write!(
f,
"The specified lag exceeds the number of values in the time series"
)
}
LagError::InvalidStride => {
write!(
f,
"The specified stride value must be greater than or equal to the number of elements in the data"
)
}
LagError::InvalidLength => write!(
f,
"The number of data points does not match the row/column length specified"
),
LagError::InvalidMemoryLayout => write!(
f,
"The data is in an invalid (e.g. non-contiguous) memory layout"
),
LagError::InvalidLags => write!(f, "Invalid or no lags were specified"),
LagError::EmptyData => write!(f, "TThe data slice was emptyt"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[rustfmt::skip]
fn test_lag() {
let data = [42.0, 40.0, 38.0, 36.0];
let lag = f64::INFINITY;
let direct = lag_matrix(&data, 0..=3, lag, 0).unwrap();
let implicit = data.lag_matrix(0..=3, lag, 0).unwrap();
assert_eq!(direct.num_lags(), 4);
assert_eq!(direct.num_rows(), 4);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 4);
assert_eq!(direct.series_count(), 1);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
42.0, 40.0, 38.0, 36.0, lag, 42.0, 40.0, 38.0, lag, lag, 42.0, 40.0, lag, lag, lag, 42.0 ]
);
assert_eq!(direct, implicit);
}
#[test]
#[rustfmt::skip]
fn test_lag_2() {
let data = [42.0, 40.0, 38.0, 36.0];
let lag = f64::INFINITY;
let direct = lag_matrix(&data, [1, 3, 2], lag, 0).unwrap();
assert_eq!(direct.num_lags(), 3);
assert_eq!(direct.num_rows(), 3);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 4);
assert_eq!(direct.series_count(), 1);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
lag, 42.0, 40.0, 38.0,
lag, lag, lag, 42.0,
lag, lag, 42.0, 40.0
]
);
}
#[test]
#[rustfmt::skip]
fn test_strided_lag_1() {
let data = [42.0, 40.0, 38.0, 36.0];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix(&data, 0..=3, lag, 5).unwrap();
assert_eq!(direct.num_lags(), 4);
assert_eq!(direct.num_rows(), 4);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 5);
assert_eq!(direct.series_count(), 1);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
42.0, 40.0, 38.0, 36.0, padding, lag, 42.0, 40.0, 38.0, padding, lag, lag, 42.0, 40.0, padding, lag, lag, lag, 42.0, padding ]
);
}
#[test]
#[rustfmt::skip]
fn test_strided_lag_2() {
let data = [42.0, 40.0, 38.0, 36.0];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix(&data, 0..=3, lag, 8).unwrap();
assert_eq!(direct.num_lags(), 4);
assert_eq!(direct.num_rows(), 4);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 8);
assert_eq!(direct.series_count(), 1);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
42.0, 40.0, 38.0, 36.0, padding, padding, padding, padding, lag, 42.0, 40.0, 38.0, padding, padding, padding, padding, lag, lag, 42.0, 40.0, padding, padding, padding, padding, lag, lag, lag, 42.0, padding, padding, padding, padding ]
);
}
#[test]
#[rustfmt::skip]
fn test_lag_2d_rowwise() {
let data = [
1.0, 2.0, 3.0, 4.0,
-1.0, -2.0, -3.0, -4.0
];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix_2d(&data, MatrixLayout::RowMajor(4), 0..=3, lag, 5).unwrap();
assert_eq!(direct.num_lags(), 4);
assert_eq!(direct.num_rows(), 8);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 5);
assert_eq!(direct.series_count(), 2);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
1.0, 2.0, 3.0, 4.0, padding, -1.0, -2.0, -3.0, -4.0, padding,
lag, 1.0, 2.0, 3.0, padding, lag, -1.0, -2.0, -3.0, padding,
lag, lag, 1.0, 2.0, padding, lag, lag, -1.0, -2.0, padding,
lag, lag, lag, 1.0, padding, lag, lag, lag, -1.0, padding,
]
);
}
#[test]
#[rustfmt::skip]
fn test_lag_2d_rowwise_2() {
let data = [
1.0, 2.0, 3.0, 4.0,
-1.0, -2.0, -3.0, -4.0
];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix_2d(&data, MatrixLayout::RowMajor(4), [1, 3, 2], lag, 5).unwrap();
assert_eq!(direct.num_lags(), 3);
assert_eq!(direct.num_rows(), 6);
assert_eq!(direct.num_cols(), 4);
assert_eq!(direct.row_stride(), 5);
assert_eq!(direct.series_count(), 2);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::RowMajor(4));
assert!(direct.is_row_major());
assert_eq!(
direct,
&[
lag, 1.0, 2.0, 3.0, padding,
lag, -1.0, -2.0, -3.0, padding,
lag, lag, lag, 1.0, padding,
lag, lag, lag, -1.0, padding,
lag, lag, 1.0, 2.0, padding,
lag, lag, -1.0, -2.0, padding,
]
);
}
#[test]
#[rustfmt::skip]
fn test_lag_2d_columnwise() {
let data = [
1.0, -1.0,
2.0, -2.0,
3.0, -3.0,
4.0, -4.0
];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix_2d(&data, MatrixLayout::ColumnMajor(4), 0..=3, lag, 9).unwrap();
assert_eq!(direct.num_lags(), 4);
assert_eq!(direct.num_rows(), 4);
assert_eq!(direct.num_cols(), 8);
assert_eq!(direct.row_stride(), 9);
assert_eq!(direct.series_count(), 2);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::ColumnMajor(4));
assert!(!direct.is_row_major());
assert_eq!(
direct,
&[
1.0, -1.0, lag, lag, lag, lag, lag, lag, padding,
2.0, -2.0, 1.0, -1.0, lag, lag, lag, lag, padding,
3.0, -3.0, 2.0, -2.0, 1.0, -1.0, lag, lag, padding,
4.0, -4.0, 3.0, -3.0, 2.0, -2.0, 1.0, -1.0, padding
]
);
}
#[test]
#[rustfmt::skip]
fn test_lag_2d_columnwise_2() {
let data = [
1.0, -1.0,
2.0, -2.0,
3.0, -3.0,
4.0, -4.0
];
let lag = f64::INFINITY;
let padding = f64::INFINITY;
let direct = lag_matrix_2d(&data, MatrixLayout::ColumnMajor(4), [1, 3, 2], lag, 7).unwrap();
assert_eq!(direct.num_lags(), 3);
assert_eq!(direct.num_rows(), 4);
assert_eq!(direct.num_cols(), 6);
assert_eq!(direct.row_stride(), 7);
assert_eq!(direct.series_count(), 2);
assert_eq!(direct.series_length(), 4);
assert_eq!(direct.matrix_layout(), MatrixLayout::ColumnMajor(4));
assert!(!direct.is_row_major());
assert_eq!(
direct,
&[
lag, lag, lag, lag, lag, lag, padding,
1.0, -1.0, lag, lag, lag, lag, padding,
2.0, -2.0, lag, lag, 1.0, -1.0, padding,
3.0, -3.0, 1.0, -1.0, 2.0, -2.0, padding
]
);
}
#[test]
fn test_lag_matrix_2d_long_series_rowwise() {
let long_data_rowwise: Vec<f64> = (0..20_000).map(|i| i as f64).collect();
let lag = f64::INFINITY;
let result = lag_matrix_2d(
&long_data_rowwise,
MatrixLayout::RowMajor(20_000),
0..=999,
lag,
20_000,
);
assert!(result.is_ok(), "lag_matrix_2d returned an error");
let matrix = result.unwrap();
assert_eq!(matrix.num_rows(), 1000);
assert_eq!(matrix.num_cols(), 20000);
assert!(matrix.is_row_major());
}
}