From b728ca8809a2c05386f706837042eb35d7dc681c Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sun, 23 Mar 2025 23:41:42 -0700 Subject: [PATCH 1/7] update several matrix methods to apply to degree n matrices --- lib/Value/Matrix.pm | 218 ++++++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 67 deletions(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 4c9f457f3..f2179db79 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -103,17 +103,17 @@ Examples: =head3 Access values - row(i) : MathObjectMatrix - For a 1D tensor, produces a 1D tensor - For nD tensor with n > 1, produces a (n-1)D tensor + row(i) : MathObject Matrix + For a degree 1 Matrix, produces a degree 1 Matrix + For a degree n Matrix with n > 1, produces a degree (n-1) Matrix - column(j) : MathObjectMatrix or Real or Complex - For a 1D tensor, produces a Real or Complex - For nD tensor with n > 1, produces an nD tensor where 2nd dimension is 1 + column(j) : MathObject Matrix or Real or Complex + For a degree 1 Matrix, produces a Real or Complex + For a degree n Matrix with n > 1, produces a degree n Matrix where the 2nd dimesion is length 1 - element : Real or Complex value when passed the same number of arguments as the dimension of the tensor. - If passed more than n arguments, null. If the dimension of the tensor is n and element is passed - k arguments with k < n, then this produces the corresponding (n-k)-dimensional tensor. + element : Real/Complex/Fraction value when passed the same number of arguments as the degree of the Matrix. + If passed more than n arguments, null. If the degree of the Matrix is n and C is passed + k arguments with k < n, then this produces the corresponding degree (n-k) tensor. =head3 Update values (these need to be added) @@ -366,7 +366,7 @@ sub isSquare { =head3 C -Return true if the matix is 1-dimensional (i.e., is a matrix row) +Return true if the matix is degree 1 (i.e., is a matrix row) Usage: @@ -380,8 +380,7 @@ Usage: sub isRow { my $self = shift; - my @d = $self->dimensions; - return scalar(@d) == 1; + return $self->degree == 1; } =head3 C @@ -429,16 +428,22 @@ sub isOne { =head3 C -Check for zero matrix. +Check for zero Matrix Usage: $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [13, 14, 15, 16] ]); - $A->isZero; # is false + $A->isZero; # is false $B = Matrix([ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ]); $B->isZero; # is true; + $C = Matrix([ [ [ 1, 0 ], [ 0, 1 ] ], [ [ 1, 0 ], [ 0, 1 ] ] ]); + $C->isZero; # is false + + $D = Matrix([ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 0, 0 ] ] ]); + $D->isZero; # is true + =cut sub isZero { @@ -447,23 +452,38 @@ sub isZero { return 1; } -# -# See if the matrix is triangular, diagonal, symmetric, orthogonal -# +=head3 C + +Check if a Matrix is upper triangular (for degree > 2, applies to frontal slice matrices) + +=cut sub isUpperTriangular { my $self = shift; my @d = $self->dimensions; return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - for my $i (2 .. $d[0]) { - for my $j (1 .. ($i - 1 < $d[1] ? $i - 1 : $d[1])) { - return 0 unless $self->element($i, $j) == 0; + if (scalar(@d) == 2) { + for my $i (2 .. $d[0]) { + for my $j (1 .. ($i - 1 < $d[1] ? $i - 1 : $d[1])) { + return 0 unless $self->element($i, $j) == 0; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isUpperTriangular) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is lower triangular (for degree > 2, applies to frontal slice matrices) + +=cut + sub isLowerTriangular { my $self = shift; my @d = $self->dimensions; @@ -471,34 +491,66 @@ sub isLowerTriangular { for ((@{ $self->{data} })[ 1 .. $#{ $self->{data} } ]) { return 0 unless $_ == 0; } - } - return 0 if scalar(@d) > 2; - for my $i (1 .. $d[0] - 1) { - for my $j ($i + 1 .. $d[1]) { - return 0 unless $self->element($i, $j) == 0; + } elsif (scalar(@d) == 2) { + for my $i (1 .. $d[0]) { + for my $j ($i + 1 .. $d[1]) { + return 0 unless $self->element($i, $j) == 0; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isLowerTriangular) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is diagonal (for degree > 2, applies to frontal slice matrices) + +=cut + sub isDiagonal { my $self = shift; return $self->isSquare && $self->isUpperTriangular && $self->isLowerTriangular; } +=head3 C + +Check if a Matrix is symmetric (for degree > 2, applies to frontal slice matrices) + +=cut + sub isSymmetric { my $self = shift; return 0 unless $self->isSquare; - my $d = ($self->dimensions)[0]; - return 1 if $d == 1; - for my $i (1 .. $d - 1) { - for my $j ($i + 1 .. $d) { - return 0 unless $self->element($i, $j) == $self->element($j, $i); + my @d = $self->dimensions; + return 1 if $d[-1] == 1; + if (scalar(@d) == 2) { + for my $i (1 .. $d[0] - 1) { + for my $j ($i + 1 .. $d[0]) { + return 0 unless $self->element($i, $j) == $self->element($j, $i); + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isSymmetric) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is orthogonal (for degree > 2, applies to frontal slice matrices) + +=cut + sub isOrthogonal { my $self = shift; return 0 unless $self->isSquare; @@ -510,50 +562,77 @@ sub isOrthogonal { return $M->isOne; } -# -# See if the matrix is in (reduced) row echelon form -# +=head3 C + +Check if a Matrix is in row echelon form (for degree > 2, applies to frontal slice matrices) + +=cut sub isREF { my $self = shift; my @d = $self->dimensions; return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - my $k = 0; - for my $i (1 .. $d[0]) { - for my $j (1 .. $d[1]) { - if ($j <= $k) { - return 0 unless $self->element($i, $j) == 0; - } elsif ($self->element($i, $j) != 0) { - $k = $j; - last; - } elsif ($j == $d[1]) { - $k = $d[1] + 1; + if (scalar(@d) == 2) { + my $k = 0; + for my $i (1 .. $d[0]) { + for my $j (1 .. $d[1]) { + if ($j <= $k) { + return 0 unless $self->element($i, $j) == 0; + } elsif ($self->element($i, $j) != 0) { + $k = $j; + last; + } elsif ($j == $d[1]) { + $k = $d[1] + 1; + } + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isREF) { + return 0; } } } return 1; } +=head3 C + +Check if a Matrix is in reduced row echelon form (for degree > 2, applies to frontal slice matrices) + +=cut + sub isRREF { my $self = shift; my @d = $self->dimensions; - return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - my $k = 0; - for my $i (1 .. $d[0]) { - for my $j (1 .. $d[1]) { - if ($j <= $k) { - return 0 unless $self->element($i, $j) == 0; - } elsif ($self->element($i, $j) != 0) { - return 0 unless $self->element($i, $j) == 1; - for my $m (1 .. $i - 1) { - return 0 unless $self->element($m, $j) == 0; + if (scalar(@d) == 1) { + for my $i (1 .. $d[0]) { + next if $self->element($i) == 0; + return $self->element($i) == 1 ? 1 : 0; + } + return 1; + } elsif (scalar(@d) == 2) { + my $k = 0; + for my $i (1 .. $d[0]) { + for my $j (1 .. $d[1]) { + if ($j <= $k) { + return 0 unless $self->element($i, $j) == 0; + } elsif ($self->element($i, $j) != 0) { + return 0 unless $self->element($i, $j) == 1; + for my $m (1 .. $i - 1) { + return 0 unless $self->element($m, $j) == 0; + } + $k = $j; + last; + } elsif ($j == $d[1]) { + $k = $d[1] + 1; } - $k = $j; - last; - } elsif ($j == $d[1]) { - $k = $d[1] + 1; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isRREF) { + return 0; } } } @@ -850,7 +929,7 @@ sub I { =head3 C -Get an elementary matrix of the requested size and type. These include matrix that upon left multiply will +Get a degree 2 elementary matrix of the requested size and type. These include matrix that upon left multiply will perform row operations. =over @@ -963,7 +1042,7 @@ sub E { =head3 C

-Creates a permutation matrix of the requested size. +Creates a degree 2 permutation matrix of the requested size. C<< Value::Matrix->P(n,(cycles)) >> in general where C is a sequence of array references of the cycles. @@ -1036,7 +1115,7 @@ sub P { =head3 C -Create a zero matrix of requested size. If called on existing matrix, creates a matrix as +Create a degree 2 zero matrix of requested size. If called on existing matrix, creates a matrix as the same size as given matrix. Usage: @@ -1069,7 +1148,8 @@ sub Zero { =head3 C -Extract a given row from the matrix. +Extract a given row from the matrix. For a degree 1 Matrix, $M->row(1) will return $M itself. +Otherwise, a "row" is defined by the first index. For an degree n Matrix, a "row" will be a degree (n-1) Matrix. Usage: @@ -1090,7 +1170,9 @@ sub row { =head3 C -Extract a given column from the matrix. +Extract a given column from the matrix. For a degree 1 Matrix, C<$M->column(j)> will return the jth entry. +Otherwise, for an degree n Matrix, C<$M->column(j)> returns an degree n Matrix tensor using j for the second index. +To obtain the corresponding degree (n-1) Matrix, see C. Usage: @@ -1119,11 +1201,11 @@ sub column { =head3 C -Extract an element from the given row/col. +Extract an element from the given position. Usage: - $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); + $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); $A->element(2,3); # returns 7 $B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); @@ -1204,6 +1286,8 @@ sub det { my $self = shift; $self->wwMatrixLR; Value->Error("Can't take determinant of non-square matrix") unless $self->isSquare; + my $n = $self->degree; + Value->Error("Can't take determinant of degree $n matrix") unless ($n <= 2); return Value::makeValue($self->{lrM}->det_LR); } From bb3eb7766caed41b41904237f98c2f6fd563ec0e Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 27 Mar 2025 18:28:46 -0700 Subject: [PATCH 2/7] cleanup from PR#1216 review --- lib/Value/Matrix.pm | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f2179db79..5f53bf22f 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -418,9 +418,7 @@ sub isOne { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isOne) { - return 0; - } + return 0 unless $row->isOne; } } return 1; @@ -470,9 +468,7 @@ sub isUpperTriangular { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isUpperTriangular) { - return 0; - } + return 0 unless $row->isUpperTriangular; } } return 1; @@ -499,9 +495,7 @@ sub isLowerTriangular { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isLowerTriangular) { - return 0; - } + return 0 unless $row->isLowerTriangular; } } return 1; @@ -537,9 +531,7 @@ sub isSymmetric { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isSymmetric) { - return 0; - } + return 0 unless $row->isSymmetric; } } return 1; @@ -588,15 +580,13 @@ sub isREF { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isREF) { - return 0; - } + return 0 unless $row->isREF; } } return 1; } -=head3 C +=head3 C Check if a Matrix is in reduced row echelon form (for degree > 2, applies to frontal slice matrices) @@ -631,9 +621,7 @@ sub isRREF { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isRREF) { - return 0; - } + return 0 unless $row->isREF; } } return 1; From 3500d9146f48631abe0d2af534387ff57b35657f Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Fri, 28 Mar 2025 22:48:48 -0700 Subject: [PATCH 3/7] more tests for Matrix MathObjects --- t/math_objects/matrix.t | 71 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index 1f27f70cf..9724034e5 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -105,17 +105,84 @@ subtest 'Use isSquare, isOne, and isRow methods' => sub { }; subtest 'Use tests for triangular matrices' => sub { - my $A1 = Matrix([ [ 1, 2, 3, 4 ], [ 0, 6, 7, 8 ], [ 0, 0, 11, 12 ], [ 0, 0, 0, 16 ] ]); + my $A1 = Matrix([ [ 1, 2, 3, 4 ], [ 0, 6, 7, 8 ], [ 0, 0, 11, 12 ], [ 0, 0, 0, 16 ] ]); my $A2 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [ 13, 14, 15, 16 ] ]); + my $A3 = Matrix($A1, $A1); + my $A4 = Matrix($A2, $A1); ok $A1->isUpperTriangular, 'test for upper triangular matrix'; ok !$A2->isUpperTriangular, 'not an upper triangular matrix'; + ok $A3->isUpperTriangular, 'test for upper triangular degree 3 matrix'; + ok !$A4->isUpperTriangular, 'not an upper triangular degree 3 matrix'; my $B1 = Matrix([ [ 1, 0, 0, 0 ], [ 5, 6, 0, 0 ], [ 9, 10, 11, 0 ], [ 13, 14, 15, 16 ] ]); - ok $B1->isLowerTriangular, 'test for lower triangular matrix'; my $B2 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); + my $B3 = Matrix($B1, $B1); + my $B4 = Matrix($B2, $B2); + ok $B1->isLowerTriangular, 'test for lower triangular matrix'; ok !$B2->isLowerTriangular, 'not a lower triangular matrix.'; + ok $B3->isLowerTriangular, 'test for lower triangular degree 3 matrix'; + ok !$B4->isLowerTriangular, 'not a lower triangular degree 3 matrix.'; }; +subtest 'Test if a Matrix is symmetric' => sub { + my $A = Matrix(5); + ok $A->isSymmetric, 'test a degree 1 Matrix of length 1 is symmetric'; + my $B = Matrix([ 1, 2 ], [ 2, 3 ]); + my $C = Matrix([ 1, 2 ], [ 3, 4 ]); + ok $B->isSymmetric, 'test a degree 2 symmetric Matrix'; + ok !$C->isSymmetric, 'test a degree 2 nonsymmetric Matrix'; + my $D = Matrix($B, $B); + my $E = Matrix($B, $C); + ok $D->isSymmetric, 'test a degree 3 symmetric Matrix'; + ok !$E->isSymmetric, 'test a degree 3 nonsymmetric Matrix'; +}; + +subtest 'Test if a Matrix is orthogonal' => sub { + my $A = Matrix(-1); + my $B = Matrix( 2); + ok $A->isOrthogonal, 'test a degree 1 orthogonal Matrix'; + ok !$B->isOrthogonal, 'test a degree 1 nonorthogonal Matrix'; + my $C = Matrix([ 3 / 5, 4 / 5 ], [ -4 / 5, 3 / 5 ]); + my $D = Matrix([ 1, 2 ], [ 3, 4 ]); + ok $C->isOrthogonal, 'test a degree 2 orthogonal Matrix'; + ok !$D->isOrthogonal, 'test a degree 2 nonorthogonal Matrix'; + # uncomment these once transposition is valid for higher degree Matrices + #my $E = Matrix($C, [ [ 0, 1 ], [ -1, 0 ] ]); + #my $F = Matrix($D, $C); + #ok $E->isOrthogonal, 'test a degree 3 orthogonal Matrix'; + #ok !$F->isOrthogonal, 'test a degree 3 nonorthogonal Matrix'; +}; + +subtest 'Test if Matrix is in (R)REF' => sub { + my $A1 = Matrix(0); + my $A2 = Matrix(1); + my $A3 = Matrix(2); + my $A4 = Matrix(1, 3, 4); + my $A5 = Matrix(2, 3, 4); + my $A6 = Matrix(0, 3, 4); + my $A7 = Matrix(0, 1, 4); + ok $A1->isRREF, "$A1 is in RREF"; + ok $A2->isRREF, "$A2 is in RREF"; + ok !$A3->isRREF, "$A3 is not in RREF"; + ok $A4->isRREF, "$A4 is in RREF"; + ok !$A5->isRREF, "$A5 is not in RREF"; + ok !$A6->isRREF, "$A6 is not in RREF"; + ok $A7->isRREF, "$A7 is in RREF"; + + my $B1 = Matrix([ 1, 2, 3 ], [ 0, 4, 5 ]); + my $B2 = Matrix([ 1, 2, 3 ], [ 0, 1, 5 ]); + my $B3 = Matrix([ 1, 0, 3 ], [ 0, 1, 5 ]); + my $B4 = Matrix([ 0, 1, 3 ], [ 2, 1, 5 ]); + ok $B1->isREF, "$B1 is in REF"; + ok !$B1->isRREF, "$B1 is not in RREF"; + ok $B2->isREF, "$B2 is in REF"; + ok !$B2->isRREF, "$B2 is not in RREF"; + ok $B3->isREF, "$B3 is in REF"; + ok $B3->isRREF, "$B3 is in RREF"; + ok !$B4->isREF, "$B4 is not in REF"; + ok !$B4->isRREF, "$B4 is not in RREF"; +}; + subtest 'Transpose a Matrix' => sub { my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]); From 130bfc5803512aa5a75eb4f334e5e7fe3ad7028f Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sun, 23 Mar 2025 23:41:42 -0700 Subject: [PATCH 4/7] update several matrix methods to apply to degree n matrices --- lib/Value/Matrix.pm | 218 ++++++++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 67 deletions(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 4c9f457f3..f2179db79 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -103,17 +103,17 @@ Examples: =head3 Access values - row(i) : MathObjectMatrix - For a 1D tensor, produces a 1D tensor - For nD tensor with n > 1, produces a (n-1)D tensor + row(i) : MathObject Matrix + For a degree 1 Matrix, produces a degree 1 Matrix + For a degree n Matrix with n > 1, produces a degree (n-1) Matrix - column(j) : MathObjectMatrix or Real or Complex - For a 1D tensor, produces a Real or Complex - For nD tensor with n > 1, produces an nD tensor where 2nd dimension is 1 + column(j) : MathObject Matrix or Real or Complex + For a degree 1 Matrix, produces a Real or Complex + For a degree n Matrix with n > 1, produces a degree n Matrix where the 2nd dimesion is length 1 - element : Real or Complex value when passed the same number of arguments as the dimension of the tensor. - If passed more than n arguments, null. If the dimension of the tensor is n and element is passed - k arguments with k < n, then this produces the corresponding (n-k)-dimensional tensor. + element : Real/Complex/Fraction value when passed the same number of arguments as the degree of the Matrix. + If passed more than n arguments, null. If the degree of the Matrix is n and C is passed + k arguments with k < n, then this produces the corresponding degree (n-k) tensor. =head3 Update values (these need to be added) @@ -366,7 +366,7 @@ sub isSquare { =head3 C -Return true if the matix is 1-dimensional (i.e., is a matrix row) +Return true if the matix is degree 1 (i.e., is a matrix row) Usage: @@ -380,8 +380,7 @@ Usage: sub isRow { my $self = shift; - my @d = $self->dimensions; - return scalar(@d) == 1; + return $self->degree == 1; } =head3 C @@ -429,16 +428,22 @@ sub isOne { =head3 C -Check for zero matrix. +Check for zero Matrix Usage: $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [13, 14, 15, 16] ]); - $A->isZero; # is false + $A->isZero; # is false $B = Matrix([ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ]); $B->isZero; # is true; + $C = Matrix([ [ [ 1, 0 ], [ 0, 1 ] ], [ [ 1, 0 ], [ 0, 1 ] ] ]); + $C->isZero; # is false + + $D = Matrix([ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 0, 0 ] ] ]); + $D->isZero; # is true + =cut sub isZero { @@ -447,23 +452,38 @@ sub isZero { return 1; } -# -# See if the matrix is triangular, diagonal, symmetric, orthogonal -# +=head3 C + +Check if a Matrix is upper triangular (for degree > 2, applies to frontal slice matrices) + +=cut sub isUpperTriangular { my $self = shift; my @d = $self->dimensions; return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - for my $i (2 .. $d[0]) { - for my $j (1 .. ($i - 1 < $d[1] ? $i - 1 : $d[1])) { - return 0 unless $self->element($i, $j) == 0; + if (scalar(@d) == 2) { + for my $i (2 .. $d[0]) { + for my $j (1 .. ($i - 1 < $d[1] ? $i - 1 : $d[1])) { + return 0 unless $self->element($i, $j) == 0; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isUpperTriangular) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is lower triangular (for degree > 2, applies to frontal slice matrices) + +=cut + sub isLowerTriangular { my $self = shift; my @d = $self->dimensions; @@ -471,34 +491,66 @@ sub isLowerTriangular { for ((@{ $self->{data} })[ 1 .. $#{ $self->{data} } ]) { return 0 unless $_ == 0; } - } - return 0 if scalar(@d) > 2; - for my $i (1 .. $d[0] - 1) { - for my $j ($i + 1 .. $d[1]) { - return 0 unless $self->element($i, $j) == 0; + } elsif (scalar(@d) == 2) { + for my $i (1 .. $d[0]) { + for my $j ($i + 1 .. $d[1]) { + return 0 unless $self->element($i, $j) == 0; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isLowerTriangular) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is diagonal (for degree > 2, applies to frontal slice matrices) + +=cut + sub isDiagonal { my $self = shift; return $self->isSquare && $self->isUpperTriangular && $self->isLowerTriangular; } +=head3 C + +Check if a Matrix is symmetric (for degree > 2, applies to frontal slice matrices) + +=cut + sub isSymmetric { my $self = shift; return 0 unless $self->isSquare; - my $d = ($self->dimensions)[0]; - return 1 if $d == 1; - for my $i (1 .. $d - 1) { - for my $j ($i + 1 .. $d) { - return 0 unless $self->element($i, $j) == $self->element($j, $i); + my @d = $self->dimensions; + return 1 if $d[-1] == 1; + if (scalar(@d) == 2) { + for my $i (1 .. $d[0] - 1) { + for my $j ($i + 1 .. $d[0]) { + return 0 unless $self->element($i, $j) == $self->element($j, $i); + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isSymmetric) { + return 0; + } } } return 1; } +=head3 C + +Check if a Matrix is orthogonal (for degree > 2, applies to frontal slice matrices) + +=cut + sub isOrthogonal { my $self = shift; return 0 unless $self->isSquare; @@ -510,50 +562,77 @@ sub isOrthogonal { return $M->isOne; } -# -# See if the matrix is in (reduced) row echelon form -# +=head3 C + +Check if a Matrix is in row echelon form (for degree > 2, applies to frontal slice matrices) + +=cut sub isREF { my $self = shift; my @d = $self->dimensions; return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - my $k = 0; - for my $i (1 .. $d[0]) { - for my $j (1 .. $d[1]) { - if ($j <= $k) { - return 0 unless $self->element($i, $j) == 0; - } elsif ($self->element($i, $j) != 0) { - $k = $j; - last; - } elsif ($j == $d[1]) { - $k = $d[1] + 1; + if (scalar(@d) == 2) { + my $k = 0; + for my $i (1 .. $d[0]) { + for my $j (1 .. $d[1]) { + if ($j <= $k) { + return 0 unless $self->element($i, $j) == 0; + } elsif ($self->element($i, $j) != 0) { + $k = $j; + last; + } elsif ($j == $d[1]) { + $k = $d[1] + 1; + } + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isREF) { + return 0; } } } return 1; } +=head3 C + +Check if a Matrix is in reduced row echelon form (for degree > 2, applies to frontal slice matrices) + +=cut + sub isRREF { my $self = shift; my @d = $self->dimensions; - return 1 if scalar(@d) == 1; - return 0 if scalar(@d) > 2; - my $k = 0; - for my $i (1 .. $d[0]) { - for my $j (1 .. $d[1]) { - if ($j <= $k) { - return 0 unless $self->element($i, $j) == 0; - } elsif ($self->element($i, $j) != 0) { - return 0 unless $self->element($i, $j) == 1; - for my $m (1 .. $i - 1) { - return 0 unless $self->element($m, $j) == 0; + if (scalar(@d) == 1) { + for my $i (1 .. $d[0]) { + next if $self->element($i) == 0; + return $self->element($i) == 1 ? 1 : 0; + } + return 1; + } elsif (scalar(@d) == 2) { + my $k = 0; + for my $i (1 .. $d[0]) { + for my $j (1 .. $d[1]) { + if ($j <= $k) { + return 0 unless $self->element($i, $j) == 0; + } elsif ($self->element($i, $j) != 0) { + return 0 unless $self->element($i, $j) == 1; + for my $m (1 .. $i - 1) { + return 0 unless $self->element($m, $j) == 0; + } + $k = $j; + last; + } elsif ($j == $d[1]) { + $k = $d[1] + 1; } - $k = $j; - last; - } elsif ($j == $d[1]) { - $k = $d[1] + 1; + } + } + } else { + for my $row (@{ $self->{data} }) { + if (!$row->isRREF) { + return 0; } } } @@ -850,7 +929,7 @@ sub I { =head3 C -Get an elementary matrix of the requested size and type. These include matrix that upon left multiply will +Get a degree 2 elementary matrix of the requested size and type. These include matrix that upon left multiply will perform row operations. =over @@ -963,7 +1042,7 @@ sub E { =head3 C

-Creates a permutation matrix of the requested size. +Creates a degree 2 permutation matrix of the requested size. C<< Value::Matrix->P(n,(cycles)) >> in general where C is a sequence of array references of the cycles. @@ -1036,7 +1115,7 @@ sub P { =head3 C -Create a zero matrix of requested size. If called on existing matrix, creates a matrix as +Create a degree 2 zero matrix of requested size. If called on existing matrix, creates a matrix as the same size as given matrix. Usage: @@ -1069,7 +1148,8 @@ sub Zero { =head3 C -Extract a given row from the matrix. +Extract a given row from the matrix. For a degree 1 Matrix, $M->row(1) will return $M itself. +Otherwise, a "row" is defined by the first index. For an degree n Matrix, a "row" will be a degree (n-1) Matrix. Usage: @@ -1090,7 +1170,9 @@ sub row { =head3 C -Extract a given column from the matrix. +Extract a given column from the matrix. For a degree 1 Matrix, C<$M->column(j)> will return the jth entry. +Otherwise, for an degree n Matrix, C<$M->column(j)> returns an degree n Matrix tensor using j for the second index. +To obtain the corresponding degree (n-1) Matrix, see C. Usage: @@ -1119,11 +1201,11 @@ sub column { =head3 C -Extract an element from the given row/col. +Extract an element from the given position. Usage: - $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); + $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); $A->element(2,3); # returns 7 $B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); @@ -1204,6 +1286,8 @@ sub det { my $self = shift; $self->wwMatrixLR; Value->Error("Can't take determinant of non-square matrix") unless $self->isSquare; + my $n = $self->degree; + Value->Error("Can't take determinant of degree $n matrix") unless ($n <= 2); return Value::makeValue($self->{lrM}->det_LR); } From 0aba6d13c0e39a22f972460dbd9e837da225cba4 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 27 Mar 2025 18:28:46 -0700 Subject: [PATCH 5/7] cleanup from PR#1216 review --- lib/Value/Matrix.pm | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index f2179db79..5f53bf22f 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -418,9 +418,7 @@ sub isOne { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isOne) { - return 0; - } + return 0 unless $row->isOne; } } return 1; @@ -470,9 +468,7 @@ sub isUpperTriangular { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isUpperTriangular) { - return 0; - } + return 0 unless $row->isUpperTriangular; } } return 1; @@ -499,9 +495,7 @@ sub isLowerTriangular { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isLowerTriangular) { - return 0; - } + return 0 unless $row->isLowerTriangular; } } return 1; @@ -537,9 +531,7 @@ sub isSymmetric { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isSymmetric) { - return 0; - } + return 0 unless $row->isSymmetric; } } return 1; @@ -588,15 +580,13 @@ sub isREF { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isREF) { - return 0; - } + return 0 unless $row->isREF; } } return 1; } -=head3 C +=head3 C Check if a Matrix is in reduced row echelon form (for degree > 2, applies to frontal slice matrices) @@ -631,9 +621,7 @@ sub isRREF { } } else { for my $row (@{ $self->{data} }) { - if (!$row->isRREF) { - return 0; - } + return 0 unless $row->isREF; } } return 1; From f36e52be845cf763b62da2ae8c1ea9001db51eb9 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Fri, 28 Mar 2025 22:48:48 -0700 Subject: [PATCH 6/7] more tests for Matrix MathObjects --- t/math_objects/matrix.t | 71 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index 1f27f70cf..9724034e5 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -105,17 +105,84 @@ subtest 'Use isSquare, isOne, and isRow methods' => sub { }; subtest 'Use tests for triangular matrices' => sub { - my $A1 = Matrix([ [ 1, 2, 3, 4 ], [ 0, 6, 7, 8 ], [ 0, 0, 11, 12 ], [ 0, 0, 0, 16 ] ]); + my $A1 = Matrix([ [ 1, 2, 3, 4 ], [ 0, 6, 7, 8 ], [ 0, 0, 11, 12 ], [ 0, 0, 0, 16 ] ]); my $A2 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [ 13, 14, 15, 16 ] ]); + my $A3 = Matrix($A1, $A1); + my $A4 = Matrix($A2, $A1); ok $A1->isUpperTriangular, 'test for upper triangular matrix'; ok !$A2->isUpperTriangular, 'not an upper triangular matrix'; + ok $A3->isUpperTriangular, 'test for upper triangular degree 3 matrix'; + ok !$A4->isUpperTriangular, 'not an upper triangular degree 3 matrix'; my $B1 = Matrix([ [ 1, 0, 0, 0 ], [ 5, 6, 0, 0 ], [ 9, 10, 11, 0 ], [ 13, 14, 15, 16 ] ]); - ok $B1->isLowerTriangular, 'test for lower triangular matrix'; my $B2 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); + my $B3 = Matrix($B1, $B1); + my $B4 = Matrix($B2, $B2); + ok $B1->isLowerTriangular, 'test for lower triangular matrix'; ok !$B2->isLowerTriangular, 'not a lower triangular matrix.'; + ok $B3->isLowerTriangular, 'test for lower triangular degree 3 matrix'; + ok !$B4->isLowerTriangular, 'not a lower triangular degree 3 matrix.'; }; +subtest 'Test if a Matrix is symmetric' => sub { + my $A = Matrix(5); + ok $A->isSymmetric, 'test a degree 1 Matrix of length 1 is symmetric'; + my $B = Matrix([ 1, 2 ], [ 2, 3 ]); + my $C = Matrix([ 1, 2 ], [ 3, 4 ]); + ok $B->isSymmetric, 'test a degree 2 symmetric Matrix'; + ok !$C->isSymmetric, 'test a degree 2 nonsymmetric Matrix'; + my $D = Matrix($B, $B); + my $E = Matrix($B, $C); + ok $D->isSymmetric, 'test a degree 3 symmetric Matrix'; + ok !$E->isSymmetric, 'test a degree 3 nonsymmetric Matrix'; +}; + +subtest 'Test if a Matrix is orthogonal' => sub { + my $A = Matrix(-1); + my $B = Matrix( 2); + ok $A->isOrthogonal, 'test a degree 1 orthogonal Matrix'; + ok !$B->isOrthogonal, 'test a degree 1 nonorthogonal Matrix'; + my $C = Matrix([ 3 / 5, 4 / 5 ], [ -4 / 5, 3 / 5 ]); + my $D = Matrix([ 1, 2 ], [ 3, 4 ]); + ok $C->isOrthogonal, 'test a degree 2 orthogonal Matrix'; + ok !$D->isOrthogonal, 'test a degree 2 nonorthogonal Matrix'; + # uncomment these once transposition is valid for higher degree Matrices + #my $E = Matrix($C, [ [ 0, 1 ], [ -1, 0 ] ]); + #my $F = Matrix($D, $C); + #ok $E->isOrthogonal, 'test a degree 3 orthogonal Matrix'; + #ok !$F->isOrthogonal, 'test a degree 3 nonorthogonal Matrix'; +}; + +subtest 'Test if Matrix is in (R)REF' => sub { + my $A1 = Matrix(0); + my $A2 = Matrix(1); + my $A3 = Matrix(2); + my $A4 = Matrix(1, 3, 4); + my $A5 = Matrix(2, 3, 4); + my $A6 = Matrix(0, 3, 4); + my $A7 = Matrix(0, 1, 4); + ok $A1->isRREF, "$A1 is in RREF"; + ok $A2->isRREF, "$A2 is in RREF"; + ok !$A3->isRREF, "$A3 is not in RREF"; + ok $A4->isRREF, "$A4 is in RREF"; + ok !$A5->isRREF, "$A5 is not in RREF"; + ok !$A6->isRREF, "$A6 is not in RREF"; + ok $A7->isRREF, "$A7 is in RREF"; + + my $B1 = Matrix([ 1, 2, 3 ], [ 0, 4, 5 ]); + my $B2 = Matrix([ 1, 2, 3 ], [ 0, 1, 5 ]); + my $B3 = Matrix([ 1, 0, 3 ], [ 0, 1, 5 ]); + my $B4 = Matrix([ 0, 1, 3 ], [ 2, 1, 5 ]); + ok $B1->isREF, "$B1 is in REF"; + ok !$B1->isRREF, "$B1 is not in RREF"; + ok $B2->isREF, "$B2 is in REF"; + ok !$B2->isRREF, "$B2 is not in RREF"; + ok $B3->isREF, "$B3 is in REF"; + ok $B3->isRREF, "$B3 is in RREF"; + ok !$B4->isREF, "$B4 is not in REF"; + ok !$B4->isRREF, "$B4 is not in RREF"; +}; + subtest 'Transpose a Matrix' => sub { my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]); From a733d6a56836a65da6c0d405560e53775551bfa0 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Tue, 1 Apr 2025 12:54:05 -0700 Subject: [PATCH 7/7] remove Data::Dumper from matrix testing --- t/math_objects/matrix.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index 9724034e5..c2f42daa0 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -14,7 +14,7 @@ do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; loadMacros('MathObjects.pl'); Context('Matrix'); -use Data::Dumper; + subtest 'Creating a degree 1 Matrix (row vector)' => sub { ok my $M1 = Matrix(1, 2, 3), 'Create a row vector'; is $M1->class, 'Matrix', 'M1 is a Matrix';