ratio_matrix/
lib.rs

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