ratio_matrix/
lib.rs

1//! Ratio's matrix data library
2
3use std::marker::PhantomData;
4use std::ops::{Add, AddAssign, Range, Sub, SubAssign};
5
6use snafu::prelude::*;
7
8use crate::traits::{Contains2D, Dimensions2D, Domain2D, Matrix2D, MatrixMut, MatrixRef};
9
10pub mod traits;
11
12#[cfg(test)]
13mod tests;
14
15/// Matrix dimensions don't agree. Values ({n_values}) modulo columns ({n_cols}) should be 0.
16#[derive(Clone, Debug, Snafu)]
17pub struct MatrixDimensionError {
18    /// Number of values.
19    pub n_values: usize,
20    /// Invalid number of columns in a row.
21    pub n_cols: usize,
22}
23
24/// Matrix view out of bounds.
25#[derive(Clone, Debug, Snafu)]
26pub struct MatrixViewOutOfBoundsError {
27    /// Input data dimensions.
28    pub dimensions: MatrixRange,
29    /// Invalid range.
30    pub range: MatrixRange,
31}
32
33/// Matrix cell access out of bounds.
34#[derive(Clone, Debug, Snafu)]
35pub struct CoordinatesOutOfBoundsError {
36    /// Matrix data area.
37    pub range: MatrixRange,
38    /// Attempted access coordinates.
39    pub coordinates: MatrixCoordinates,
40}
41
42/// Two-dimensional matrix.
43#[derive(Clone, Debug, Default, PartialEq)]
44#[cfg_attr(
45    feature = "serde",
46    derive(serde::Serialize, serde::Deserialize),
47    serde(default, rename_all = "camelCase")
48)]
49pub struct Matrix<C> {
50    /// Matrix values as a single vector.
51    pub values: Vec<C>,
52    /// Matrix dimensions.
53    pub dimensions: MatrixDimensions,
54}
55impl<C> Matrix<C> {
56    /// Get the vector index based on a set of coordinates.
57    pub fn index<T: Into<MatrixCoordinates>>(
58        &self,
59        coordinates: T,
60    ) -> Result<usize, CoordinatesOutOfBoundsError> {
61        let x: MatrixCoordinates = coordinates.into();
62        if self.range().contains(&x) {
63            Ok(x.row * self.n_cols() + x.col)
64        } else {
65            Err(CoordinatesOutOfBoundsError {
66                coordinates: x,
67                range: self.range(),
68            })
69        }
70    }
71    /// Slice this matrix to these rows and columns.
72    pub fn slice<R: Into<MatrixRange>>(
73        &self,
74        range: R,
75    ) -> Result<MatrixSlice<'_, C>, MatrixSliceError> {
76        let range: MatrixRange = range.into();
77        if self.range().contains(&range) {
78            Ok(MatrixSlice {
79                values: &self.values,
80                input_dimensions: self.dimensions,
81                view_range: range,
82            })
83        } else {
84            MatrixViewOutOfBoundsSnafu {
85                range,
86                dimensions: self.dimensions,
87            }
88            .fail()
89            .context(SliceRangeOutOfBoundsSnafu)
90        }
91    }
92}
93impl<C> Matrix2D<C> for Matrix<C> {
94    fn new(values: Vec<C>, n_cols: usize) -> Result<Self, MatrixDimensionError> {
95        let n_values = values.len();
96        let dimensions = MatrixDimensions::new(n_values / n_cols, n_cols);
97        if dimensions.len() == n_values {
98            Ok(Self { values, dimensions })
99        } else {
100            MatrixDimensionSnafu { n_values, n_cols }.fail()
101        }
102    }
103}
104impl<C> MatrixRef<C> for Matrix<C> {
105    fn get<T: Into<MatrixCoordinates>>(
106        &self,
107        coordinates: T,
108    ) -> Result<&C, CoordinatesOutOfBoundsError> {
109        let x: MatrixCoordinates = coordinates.into();
110        let index = self.index(x)?;
111        self.values.get(index).context(CoordinatesOutOfBoundsSnafu {
112            range: self.range(),
113            coordinates: x,
114        })
115    }
116}
117impl<C> MatrixMut<C> for Matrix<C> {
118    fn set<T: Into<MatrixCoordinates>>(
119        &mut self,
120        coordinates: T,
121        value: C,
122    ) -> Result<C, CoordinatesOutOfBoundsError> {
123        let coordinates: MatrixCoordinates = coordinates.into();
124        let index = self.index(coordinates)?;
125        if let Some(entry) = self.values.get_mut(index) {
126            Ok(std::mem::replace(entry, value))
127        } else {
128            Err(CoordinatesOutOfBoundsError {
129                range: self.range(),
130                coordinates,
131            })
132        }
133    }
134}
135impl<C> Domain2D for Matrix<C> {
136    fn row_range(&self) -> Range<usize> {
137        0..self.dimensions.n_rows
138    }
139
140    fn col_range(&self) -> Range<usize> {
141        0..self.dimensions.n_cols
142    }
143}
144
145/// Matrix dimensions don't agree. Values ({n_values}) modulo columns ({n_cols}) should be 0.
146#[derive(Clone, Debug, Snafu)]
147pub enum MatrixSliceError {
148    SliceInputDimension { source: MatrixDimensionError },
149    SliceRangeOutOfBounds { source: MatrixViewOutOfBoundsError },
150}
151
152/// Slice of a matrix.
153#[derive(Clone, Debug, Default, PartialEq)]
154pub struct MatrixSlice<'a, C> {
155    /// Input matrix values as a single slice reference.
156    pub values: &'a [C],
157    /// Dimensions of the input data.
158    pub input_dimensions: MatrixDimensions,
159    /// Slice or view range with respect to the input data.
160    pub view_range: MatrixRange,
161}
162impl<'a, C> MatrixSlice<'a, C> {
163    /// Create a new slice from a matrix. Takes a reference to the input values' slice and the
164    /// number of columns in each row as well as the intended range of the slice.
165    pub fn new<R: Into<MatrixRange>>(
166        values: &'a [C],
167        n_cols: usize,
168        range: R,
169    ) -> Result<Self, MatrixSliceError> {
170        let n_values = values.len();
171        let n_rows = n_values / n_cols;
172        let input_dimensions = MatrixDimensions::new(n_rows, n_cols);
173        if values.len() != input_dimensions.len() {
174            return MatrixDimensionSnafu { n_values, n_cols }
175                .fail()
176                .context(SliceInputDimensionSnafu);
177        }
178        let domain: MatrixRange = input_dimensions.into();
179        let range: MatrixRange = range.into();
180        if domain.contains(&range) {
181            Ok(Self {
182                values,
183                input_dimensions,
184                view_range: range,
185            })
186        } else {
187            MatrixViewOutOfBoundsSnafu {
188                dimensions: input_dimensions,
189                range,
190            }
191            .fail()
192            .context(SliceRangeOutOfBoundsSnafu)
193        }
194    }
195
196    /// Get the index for item access in the reference slice.
197    /// The provided coordinates should be with respect to the slice's start!
198    pub fn index<T: Into<MatrixCoordinates>>(
199        &self,
200        coordinates: T,
201    ) -> Result<usize, CoordinatesOutOfBoundsError> {
202        let x: MatrixCoordinates = coordinates.into();
203        let x = x + self.view_range.start;
204        if self.view_range.contains(&x) {
205            Ok(x.row * self.input_dimensions.n_cols + x.col)
206        } else {
207            Err(CoordinatesOutOfBoundsError {
208                coordinates: x,
209                range: self.view_range,
210            })
211        }
212    }
213}
214
215impl<'a, C> MatrixSlice<'a, C>
216where
217    C: Clone + Default,
218{
219    /// Clone the data into this slice into a matrix of it's own size.
220    pub fn to_matrix(&self) -> Matrix<C> {
221        Matrix::new(
222            self.row_range()
223                .flat_map(|row| {
224                    self.col_range().filter_map(move |col| {
225                        self.get(MatrixCoordinates::new(row, col)).ok().cloned()
226                    })
227                })
228                .collect(),
229            self.view_range.col_range().end,
230        )
231        .unwrap_or_default()
232    }
233}
234impl<'a, C> MatrixRef<C> for MatrixSlice<'a, C> {
235    fn get<T: Into<MatrixCoordinates>>(
236        &self,
237        coordinates: T,
238    ) -> Result<&C, CoordinatesOutOfBoundsError> {
239        let coordinates: MatrixCoordinates = coordinates.into();
240        let index = self.index(coordinates)?;
241        self.values.get(index).context(CoordinatesOutOfBoundsSnafu {
242            range: self.view_range,
243            coordinates,
244        })
245    }
246}
247impl<'a, C> Domain2D for MatrixSlice<'a, C> {
248    fn row_range(&self) -> Range<usize> {
249        0..(self.view_range.end.row - self.view_range.start.row)
250    }
251    fn col_range(&self) -> Range<usize> {
252        0..(self.view_range.end.col - self.view_range.start.col)
253    }
254}
255
256/// Matrix cell.
257#[derive(Clone, Debug, PartialEq)]
258pub struct MatrixCellRef<'a, M: MatrixRef<C> + ?Sized, C> {
259    /// Matrix to pull values from.
260    pub matrix: &'a M,
261    /// Coordinates of this cell.
262    pub coordinates: MatrixCoordinates,
263    /// Phantom data marker for the cell value type.
264    _phantom: PhantomData<C>,
265}
266impl<'a, M: MatrixRef<C> + ?Sized, C> MatrixCellRef<'a, M, C> {
267    /// Create a new cell reference using the given coordinates.
268    pub fn new<T: Into<MatrixCoordinates>>(matrix: &'a M, coordinates: T) -> Self {
269        Self {
270            matrix,
271            coordinates: coordinates.into(),
272            _phantom: PhantomData,
273        }
274    }
275}
276
277/// Location in a 2D matrix.
278#[derive(Copy, Clone, Debug, PartialEq)]
279#[cfg_attr(
280    feature = "serde",
281    derive(serde::Serialize, serde::Deserialize),
282    serde(rename_all = "camelCase")
283)]
284pub enum MatrixLocation {
285    /// Entire row in a matrix.
286    Row(usize),
287    /// Entire column in a matrix.
288    Col(usize),
289    /// Specific coordinates in a matrix.
290    Coordinates(MatrixCoordinates),
291    /// 2D area in a matrix.
292    Range(MatrixRange),
293}
294impl Default for MatrixLocation {
295    fn default() -> Self {
296        Self::Coordinates(Default::default())
297    }
298}
299impl From<MatrixCoordinates> for MatrixLocation {
300    fn from(value: MatrixCoordinates) -> Self {
301        Self::Coordinates(value)
302    }
303}
304impl From<MatrixRange> for MatrixLocation {
305    fn from(value: MatrixRange) -> Self {
306        Self::Range(value)
307    }
308}
309impl MatrixLocation {
310    /// Create a new optional location from an optional row and column coordinate.
311    pub fn from_coords(row: Option<usize>, col: Option<usize>) -> Option<Self> {
312        match (row, col) {
313            (Some(row), Some(col)) => Some(Self::Coordinates(MatrixCoordinates::new(row, col))),
314            (Some(row), None) => Some(Self::Row(row)),
315            (None, Some(col)) => Some(Self::Col(col)),
316            (None, None) => None,
317        }
318    }
319    /// Row or starting row of this location.
320    pub fn row(&self) -> Option<usize> {
321        match self {
322            Self::Row(row) => Some(*row),
323            Self::Col(_) => None,
324            Self::Coordinates(coord) => Some(coord.row),
325            Self::Range(area) => Some(area.row_range().start),
326        }
327    }
328    /// Column or starting column of this location.
329    pub fn col(&self) -> Option<usize> {
330        match self {
331            Self::Row(_) => None,
332            Self::Col(col) => Some(*col),
333            Self::Coordinates(coord) => Some(coord.col),
334            Self::Range(area) => Some(area.col_range().start),
335        }
336    }
337}
338impl Add<MatrixCoordinates> for MatrixLocation {
339    type Output = MatrixLocation;
340    fn add(self, rhs: MatrixCoordinates) -> Self::Output {
341        match self {
342            Self::Col(col) => Self::Col(col + rhs.col),
343            Self::Coordinates(x) => Self::Coordinates(x + rhs),
344            Self::Range(range) => Self::Range(range + rhs),
345            Self::Row(row) => Self::Row(row + rhs.row),
346        }
347    }
348}
349impl AddAssign<MatrixCoordinates> for MatrixLocation {
350    fn add_assign(&mut self, rhs: MatrixCoordinates) {
351        match self {
352            Self::Col(col) => col.add_assign(rhs.col),
353            Self::Coordinates(x) => x.add_assign(rhs),
354            Self::Range(range) => range.add_assign(rhs),
355            Self::Row(row) => row.add_assign(rhs.row),
356        };
357    }
358}
359impl Sub<MatrixCoordinates> for MatrixLocation {
360    type Output = MatrixLocation;
361    fn sub(self, rhs: MatrixCoordinates) -> Self::Output {
362        match self {
363            Self::Col(col) => Self::Col(col - rhs.col),
364            Self::Coordinates(x) => Self::Coordinates(x - rhs),
365            Self::Range(range) => Self::Range(range - rhs),
366            Self::Row(row) => Self::Row(row - rhs.row),
367        }
368    }
369}
370impl SubAssign<MatrixCoordinates> for MatrixLocation {
371    fn sub_assign(&mut self, rhs: MatrixCoordinates) {
372        match self {
373            Self::Col(col) => col.sub_assign(rhs.col),
374            Self::Coordinates(x) => x.sub_assign(rhs),
375            Self::Range(range) => range.sub_assign(rhs),
376            Self::Row(row) => row.sub_assign(rhs.row),
377        };
378    }
379}
380impl Add<MatrixLocation> for MatrixLocation {
381    type Output = MatrixLocation;
382    fn add(self, rhs: MatrixLocation) -> Self::Output {
383        match self {
384            Self::Col(col) => Self::Col(col + rhs.col().unwrap_or_default()),
385            Self::Coordinates(x) => Self::Coordinates(
386                x + MatrixCoordinates::new(
387                    rhs.row().unwrap_or_default(),
388                    rhs.col().unwrap_or_default(),
389                ),
390            ),
391            Self::Range(range) => Self::Range(
392                range
393                    + MatrixCoordinates::new(
394                        rhs.row().unwrap_or_default(),
395                        rhs.col().unwrap_or_default(),
396                    ),
397            ),
398            Self::Row(row) => Self::Row(row + rhs.row().unwrap_or_default()),
399        }
400    }
401}
402impl AddAssign<MatrixLocation> for MatrixLocation {
403    fn add_assign(&mut self, rhs: MatrixLocation) {
404        match self {
405            Self::Col(col) => col.add_assign(rhs.col().unwrap_or_default()),
406            Self::Coordinates(x) => x.add_assign(MatrixCoordinates::new(
407                rhs.row().unwrap_or_default(),
408                rhs.col().unwrap_or_default(),
409            )),
410            Self::Range(range) => range.add_assign(MatrixCoordinates::new(
411                rhs.row().unwrap_or_default(),
412                rhs.col().unwrap_or_default(),
413            )),
414            Self::Row(row) => row.add_assign(rhs.row().unwrap_or_default()),
415        }
416    }
417}
418impl Sub<MatrixLocation> for MatrixLocation {
419    type Output = MatrixLocation;
420    fn sub(self, rhs: MatrixLocation) -> Self::Output {
421        match self {
422            Self::Col(col) => Self::Col(col - rhs.col().unwrap_or_default()),
423            Self::Coordinates(x) => Self::Coordinates(
424                x - MatrixCoordinates::new(
425                    rhs.row().unwrap_or_default(),
426                    rhs.col().unwrap_or_default(),
427                ),
428            ),
429            Self::Range(range) => Self::Range(
430                range
431                    - MatrixCoordinates::new(
432                        rhs.row().unwrap_or_default(),
433                        rhs.col().unwrap_or_default(),
434                    ),
435            ),
436            Self::Row(row) => Self::Row(row - rhs.row().unwrap_or_default()),
437        }
438    }
439}
440impl SubAssign<MatrixLocation> for MatrixLocation {
441    fn sub_assign(&mut self, rhs: MatrixLocation) {
442        match self {
443            Self::Col(col) => col.sub_assign(rhs.col().unwrap_or_default()),
444            Self::Coordinates(x) => x.sub_assign(MatrixCoordinates::new(
445                rhs.row().unwrap_or_default(),
446                rhs.col().unwrap_or_default(),
447            )),
448            Self::Range(range) => range.sub_assign(MatrixCoordinates::new(
449                rhs.row().unwrap_or_default(),
450                rhs.col().unwrap_or_default(),
451            )),
452            Self::Row(row) => row.sub_assign(rhs.row().unwrap_or_default()),
453        }
454    }
455}
456impl Domain2D for MatrixLocation {
457    fn row_range(&self) -> Range<usize> {
458        match self {
459            Self::Coordinates(coordinates) => Range {
460                start: coordinates.row,
461                end: coordinates.row + 1,
462            },
463            Self::Row(row) => Range {
464                start: *row,
465                end: row + 1,
466            },
467            Self::Col(_) => Range { start: 0, end: 0 },
468            Self::Range(range) => range.row_range(),
469        }
470    }
471    fn col_range(&self) -> Range<usize> {
472        match self {
473            Self::Coordinates(coordinates) => Range {
474                start: coordinates.col,
475                end: coordinates.col + 1,
476            },
477            Self::Row(_) => Range { start: 0, end: 0 },
478            Self::Col(col) => Range {
479                start: *col,
480                end: col + 1,
481            },
482            Self::Range(range) => range.col_range(),
483        }
484    }
485}
486
487/// Coordinate in a 2D matrix.
488#[derive(Copy, Clone, Debug, Default, PartialEq)]
489#[cfg_attr(
490    feature = "serde",
491    derive(serde::Serialize, serde::Deserialize),
492    serde(default, rename_all = "camelCase")
493)]
494pub struct MatrixCoordinates {
495    /// Coordinate row.
496    pub row: usize,
497    /// Coordinate column.
498    pub col: usize,
499}
500impl MatrixCoordinates {
501    /// Create a new matrix coordinate instance.
502    pub fn new(row: usize, col: usize) -> Self {
503        Self { row, col }
504    }
505}
506impl From<(usize, usize)> for MatrixCoordinates {
507    fn from(value: (usize, usize)) -> Self {
508        Self {
509            row: value.0,
510            col: value.1,
511        }
512    }
513}
514impl Add<MatrixCoordinates> for MatrixCoordinates {
515    type Output = Self;
516    fn add(self, rhs: MatrixCoordinates) -> Self::Output {
517        MatrixCoordinates {
518            row: self.row + rhs.row,
519            col: self.col + rhs.col,
520        }
521    }
522}
523impl AddAssign<MatrixCoordinates> for MatrixCoordinates {
524    fn add_assign(&mut self, rhs: MatrixCoordinates) {
525        self.row += rhs.row;
526        self.col += rhs.col;
527    }
528}
529impl Sub<MatrixCoordinates> for MatrixCoordinates {
530    type Output = Self;
531    fn sub(self, rhs: MatrixCoordinates) -> Self::Output {
532        MatrixCoordinates {
533            row: self.row - rhs.row,
534            col: self.col - rhs.col,
535        }
536    }
537}
538impl SubAssign<MatrixCoordinates> for MatrixCoordinates {
539    fn sub_assign(&mut self, rhs: MatrixCoordinates) {
540        self.row -= rhs.row;
541        self.col -= rhs.col;
542    }
543}
544
545/// 2D matrix dimensions.
546#[derive(Copy, Clone, Debug, Default, PartialEq)]
547#[cfg_attr(
548    feature = "serde",
549    derive(serde::Serialize, serde::Deserialize),
550    serde(default, rename_all = "camelCase")
551)]
552pub struct MatrixDimensions {
553    /// Coordinate row.
554    pub n_rows: usize,
555    /// Coordinate column.
556    pub n_cols: usize,
557}
558impl MatrixDimensions {
559    /// Create a new matrix dimensions instance.
560    pub fn new(n_rows: usize, n_cols: usize) -> Self {
561        Self { n_rows, n_cols }
562    }
563}
564impl From<(usize, usize)> for MatrixDimensions {
565    fn from(value: (usize, usize)) -> Self {
566        Self {
567            n_rows: value.0,
568            n_cols: value.1,
569        }
570    }
571}
572impl Dimensions2D for MatrixDimensions {
573    fn n_rows(&self) -> usize {
574        self.n_rows
575    }
576    fn n_cols(&self) -> usize {
577        self.n_cols
578    }
579}
580impl Add<MatrixDimensions> for MatrixDimensions {
581    type Output = Self;
582    fn add(self, rhs: MatrixDimensions) -> Self::Output {
583        MatrixDimensions {
584            n_rows: self.n_rows + rhs.n_rows,
585            n_cols: self.n_cols + rhs.n_cols,
586        }
587    }
588}
589impl AddAssign<MatrixDimensions> for MatrixDimensions {
590    fn add_assign(&mut self, rhs: MatrixDimensions) {
591        self.n_rows += rhs.n_rows;
592        self.n_cols += rhs.n_cols;
593    }
594}
595impl Sub<MatrixDimensions> for MatrixDimensions {
596    type Output = Self;
597    fn sub(self, rhs: MatrixDimensions) -> Self::Output {
598        MatrixDimensions {
599            n_rows: self.n_rows - rhs.n_rows,
600            n_cols: self.n_cols - rhs.n_cols,
601        }
602    }
603}
604impl SubAssign<MatrixDimensions> for MatrixDimensions {
605    fn sub_assign(&mut self, rhs: MatrixDimensions) {
606        self.n_rows -= rhs.n_rows;
607        self.n_cols -= rhs.n_cols;
608    }
609}
610
611/// 2D range in a matrix.
612#[derive(Copy, Clone, Debug, Default, PartialEq)]
613#[cfg_attr(
614    feature = "serde",
615    derive(serde::Serialize, serde::Deserialize),
616    serde(default, rename_all = "camelCase")
617)]
618pub struct MatrixRange {
619    /// Starting coordinates of the range (inclusive).
620    pub start: MatrixCoordinates,
621    /// Ending coordinates of the range (exclusive).
622    pub end: MatrixCoordinates,
623}
624impl MatrixRange {
625    /// Create a new matrix area instance.
626    pub fn new(rows: Range<usize>, cols: Range<usize>) -> Self {
627        Self {
628            start: MatrixCoordinates::new(rows.start, cols.start),
629            end: MatrixCoordinates::new(rows.end, cols.end),
630        }
631    }
632}
633impl Domain2D for MatrixRange {
634    /// Row range of this area.
635    fn row_range(&self) -> Range<usize> {
636        self.start.row..self.end.row
637    }
638
639    /// Column range of this area.
640    fn col_range(&self) -> Range<usize> {
641        self.start.col..self.end.col
642    }
643}
644impl From<(Range<usize>, Range<usize>)> for MatrixRange {
645    fn from(value: (Range<usize>, Range<usize>)) -> Self {
646        Self::new(value.0, value.1)
647    }
648}
649impl From<((usize, usize), (usize, usize))> for MatrixRange {
650    /// Create a range from two sets of coordinates (start, end).
651    fn from(value: ((usize, usize), (usize, usize))) -> Self {
652        Self {
653            start: value.0.into(),
654            end: value.1.into(),
655        }
656    }
657}
658impl From<MatrixDimensions> for MatrixRange {
659    fn from(value: MatrixDimensions) -> Self {
660        Self::new(0..value.n_rows, 0..value.n_cols)
661    }
662}
663impl Add<MatrixCoordinates> for MatrixRange {
664    type Output = Self;
665    fn add(self, rhs: MatrixCoordinates) -> Self::Output {
666        Self {
667            start: self.start + rhs,
668            end: self.end + rhs,
669        }
670    }
671}
672impl AddAssign<MatrixCoordinates> for MatrixRange {
673    fn add_assign(&mut self, rhs: MatrixCoordinates) {
674        self.start += rhs;
675        self.end += rhs;
676    }
677}
678impl Sub<MatrixCoordinates> for MatrixRange {
679    type Output = Self;
680    fn sub(self, rhs: MatrixCoordinates) -> Self::Output {
681        Self {
682            start: self.start - rhs,
683            end: self.end - rhs,
684        }
685    }
686}
687impl SubAssign<MatrixCoordinates> for MatrixRange {
688    fn sub_assign(&mut self, rhs: MatrixCoordinates) {
689        self.start -= rhs;
690        self.end -= rhs;
691    }
692}