Skip to content

Commit 35477fd

Browse files
Take part of a matrix by range (#443) (#683)
a[1.., ..] is every row of a matrix but the first, a[..2, 1..3] the first two rows of its second and third columns, and a[^1.., ^1..] its bottom right entry. The result is an ordinary matrix sharing nothing with the one it came from. Both extents have to be written out, which is what the issue's own discussion asks for. A matrix indexed by one number gives a row while a vector indexed by one number gives an element, so a single range would have to mean one thing here and the other there; there is no one-range indexer to write, so the question cannot come up. An empty range is refused rather than answered with a matrix of no rows, since there is no such matrix; a range reaching past the matrix throws from the range itself. Ten tests, 3890 in all, 127 F#.
1 parent fa87f01 commit 35477fd

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

‎Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,39 @@ private static Matrix ToMatrix(Entity entity)
144144
/// </summary>
145145
public Entity this[int x, int y] => InnerMatrix[x, y];
146146

147+
/// <summary>
148+
/// The submatrix spanned by the given rows and columns, for example
149+
/// <code>a[1.., ..]</code> for every row of a matrix but the first, or
150+
/// <code>a[..2, 1..3]</code> for the first two rows of its second and third columns.
151+
/// </summary>
152+
/// <remarks>
153+
/// Both extents have to be written out. A matrix indexed by one number gives a row
154+
/// while a vector indexed by one number gives an element, so a single range would
155+
/// have to mean one thing here and the other there --
156+
/// https://github.com/asc-community/AngouriMath/issues/443.
157+
/// </remarks>
158+
/// <exception cref="ArgumentOutOfRangeException">
159+
/// Thrown if either range reaches past the matrix.
160+
/// </exception>
161+
/// <exception cref="InvalidMatrixOperationException">
162+
/// Thrown if either range is empty, since a matrix has at least one row and one
163+
/// column.
164+
/// </exception>
165+
public Matrix this[Range rows, Range columns]
166+
{
167+
get
168+
{
169+
var (firstRow, rowCount) = rows.GetOffsetAndLength(RowCount);
170+
var (firstColumn, columnCount) = columns.GetOffsetAndLength(ColumnCount);
171+
if (rowCount is 0 || columnCount is 0)
172+
throw new InvalidMatrixOperationException(
173+
$"A {rowCount}x{columnCount} matrix cannot be created; both extents must be non-empty");
174+
return new Matrix(
175+
indices => InnerMatrix[firstRow + indices[0], firstColumn + indices[1]],
176+
rowCount, columnCount);
177+
}
178+
}
179+
147180
/// <summary>
148181
/// Checks whether the matrix only contains one
149182
/// column.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// Copyright (c) 2019-2022 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath;
10+
using AngouriMath.Core.Exceptions;
11+
using Xunit;
12+
13+
namespace AngouriMath.Tests.Algebra
14+
{
15+
/// <summary>
16+
/// Taking part of a matrix by range, as asked for in
17+
/// https://github.com/asc-community/AngouriMath/issues/443.
18+
/// </summary>
19+
public sealed class MatrixRangeTest
20+
{
21+
private static readonly Entity.Matrix ThreeByThree = MathS.Matrices.Matrix(3, 3,
22+
1, 2, 3,
23+
4, 5, 6,
24+
7, 8, 9
25+
);
26+
27+
private static readonly Entity.Matrix Column = MathS.Matrices.Vector(1, 2, 3);
28+
29+
[Fact]
30+
public void EveryRowButTheFirst() =>
31+
Assert.Equal(MathS.Matrices.Matrix(2, 3,
32+
4, 5, 6,
33+
7, 8, 9), ThreeByThree[1.., ..]);
34+
35+
[Fact]
36+
public void EveryColumnButTheLast() =>
37+
Assert.Equal(MathS.Matrices.Matrix(3, 2,
38+
1, 2,
39+
4, 5,
40+
7, 8), ThreeByThree[.., ..2]);
41+
42+
[Fact]
43+
public void AnInnerBlock() =>
44+
Assert.Equal(MathS.Matrices.Matrix(2, 2,
45+
5, 6,
46+
8, 9), ThreeByThree[1..3, 1..3]);
47+
48+
[Fact]
49+
public void CountedFromTheEnd() =>
50+
Assert.Equal(MathS.Matrices.Matrix(1, 1, 9), ThreeByThree[^1.., ^1..]);
51+
52+
[Fact]
53+
public void TheWholeMatrix() =>
54+
Assert.Equal(ThreeByThree, ThreeByThree[.., ..]);
55+
56+
// A vector is a matrix of one column, so the second extent is what a vector is cut by
57+
// rows with, and it has only the one column to ask for.
58+
[Fact]
59+
public void PartOfAVector() =>
60+
Assert.Equal(MathS.Matrices.Vector(2, 3), Column[1.., ..]);
61+
62+
// The result is a matrix in its own right and nothing of the original is shared with
63+
// it, so what is read out of it does not depend on the order the two were built in.
64+
[Fact]
65+
public void TheResultIsAMatrixLikeAnyOther()
66+
{
67+
var block = ThreeByThree[..2, 1..];
68+
Assert.Equal(2, block.RowCount);
69+
Assert.Equal(2, block.ColumnCount);
70+
Assert.Equal((Entity)6, block[1, 1]);
71+
Assert.Equal(MathS.Matrices.Matrix(2, 2,
72+
2, 5,
73+
3, 6), block.T);
74+
}
75+
76+
[Fact]
77+
public void SymbolicEntriesAreCarriedOverUntouched() =>
78+
Assert.Equal(
79+
MathS.Matrices.Matrix(1, 2, "y", "x + 1"),
80+
MathS.Matrices.Matrix(2, 2,
81+
"x", "y",
82+
"y", "x + 1")[1.., ..][.., ..]);
83+
84+
[Fact]
85+
public void AnEmptyRangeIsRefused()
86+
{
87+
Assert.Throws<InvalidMatrixOperationException>(() => ThreeByThree[1..1, ..]);
88+
Assert.Throws<InvalidMatrixOperationException>(() => ThreeByThree[.., 2..2]);
89+
}
90+
91+
[Fact]
92+
public void ARangeReachingPastTheMatrixIsRefused()
93+
{
94+
Assert.Throws<ArgumentOutOfRangeException>(() => ThreeByThree[..4, ..]);
95+
Assert.Throws<ArgumentOutOfRangeException>(() => ThreeByThree[.., 1..5]);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)