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