ratio_matrix/
lib.rs

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