Skip to content

Commit c365efe

Browse files
committed
Update subMatrix function to handle non 2D matrices.
Also fix some documentation typos and clarifications. u
1 parent eea08bb commit c365efe

File tree

2 files changed

+183
-105
lines changed

2 files changed

+183
-105
lines changed

lib/Value/Matrix.pm

+131-67
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
=head1 Value::Matrix class
88
9-
References:
9+
This is the Math Object code for a Matrix.
10+
11+
=head2 References:
1012
1113
=over
1214
@@ -18,11 +20,7 @@ References:
1820
1921
=back
2022
21-
For allowing Matrices in Fractions, see L<http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2978>
22-
23-
Context()->parens->set("[" => {formMatrix => 1});
24-
25-
=head2 Files interacting with Matrices:
23+
=head2 Matrix-Related libraries and macros:
2624
2725
=over
2826
@@ -46,7 +44,7 @@ For allowing Matrices in Fractions, see L<http://webwork.maa.org/moodle/mod/foru
4644
4745
=item L<tableau.pl>
4846
49-
=item quickMatrixEntry.pl
47+
=item L<quickMatrixEntry.pl>
5048
5149
=item L<LinearProgramming.pl>
5250
@@ -55,12 +53,18 @@ For allowing Matrices in Fractions, see L<http://webwork.maa.org/moodle/mod/foru
5553
=head2 Contexts
5654
5755
=over
58-
=item C<Matrix> -- allows students to enter [[3,4],[3,6]]
59-
-- formMatrix =>1 also allows this?
60-
=item C<Complex-Matrix> -- allows complex entries
56+
57+
=item C<Matrix>
58+
59+
Allows students to enter C<[[3,4],[3,6]]>
60+
61+
=item C<Complex-Matrix>
62+
63+
Allows complex entries
6164
6265
=back
6366
67+
6468
=head2 Creation of Matrices
6569
6670
Using the C<Matrix>, C<Vector> or C<ColumnVector> methods
@@ -69,16 +73,16 @@ Examples:
6973
7074
$M1 = Matrix([1,2],[3,4]);
7175
$M2 = Matrix([5,6],[7,8]);
72-
$v = Vector(9,10);
73-
$w = ColumnVector(9,10); # differs in how it is printed
7476
7577
Commands added in Value::matrix
7678
7779
Conversion:
80+
7881
$matrix->value produces [[3,4,5],[1,3,4]] recursive array references of numbers (not MathObjects)
7982
$matrix->wwMatrix produces CPAN MatrixReal1 matrix, used for computation subroutines
8083
8184
Information
85+
8286
$matrix->dimension: ARRAY
8387
8488
Access values
@@ -88,7 +92,8 @@ Access values
8892
element : Real or Complex value
8993
9094
Update values
91-
setElement.
95+
96+
setElement
9297
9398
See C<change_matrix_entry()> in MatrixReduce and L<http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970>
9499
@@ -128,6 +133,20 @@ The commands below are Value::Matrix B<methods> unless otherwise noted.
128133
solve_SSM
129134
solve_RM
130135
136+
=head2 Fractions in Matrices
137+
138+
One can use fractions in Matrices by including C<Context("Fraction")>. For example
139+
140+
Context("Fraction");
141+
$A = Matrix([
142+
[Fraction(1,1), Fraction(1,2), Fraction(1,3)],
143+
[Fraction(1,2), Fraction(1,3), Fraction(1,4)],
144+
[Fraction(1,3), Fraction(1,4), Fraction(1,5)]]);
145+
146+
and operations will be done using rational arithmetic. Also helpful is the method
147+
C<apply_fraction_to_matrix_entries> in the L<MatrixReduce.pl> macro. Some additional information can be
148+
found in L<https://webwork.maa.org/moodle/mod/forum/discuss.php?d=2978>.
149+
131150
=head2 methods
132151
133152
=cut
@@ -548,20 +567,17 @@ sub mult {
548567
}
549568

550569
# Make points and vectors into columns if they are on the right.
551-
552-
if (!$flag && Value::classMatch($r, 'Point', 'Vector')) { $r = ($self->promote($r))->transpose }
553-
else { $r = $self->promote($r) }
570+
$r = !$flag && Value::classMatch($r, 'Point', 'Vector') ? ($self->promote($r))->transpose : $self->promote($r);
554571

555572
if ($flag) { my $tmp = $l; $l = $r; $r = $tmp }
556573
my @dl = $l->dimensions;
557574
my @dr = $r->dimensions;
558575
if (scalar(@dl) == 1) { @dl = (1, @dl); $l = $self->make($l) }
559576
if (scalar(@dr) == 1) { @dr = (@dr, 1); $r = $self->make($r)->transpose }
560577
Value::Error("Can only multiply 2-dimensional matrices") if scalar(@dl) > 2 || scalar(@dr) > 2;
561-
Value::Error("Matrices of dimensions %dx%d and %dx%d can't be multiplied", @dl, @dr)
562-
unless ($dl[1] == $dr[0]);
578+
Value::Error("Matrices of dimensions %dx%d and %dx%d can't be multiplied", @dl, @dr) unless ($dl[1] == $dr[0]);
563579

564-
# Perform atrix multiplication.
580+
# Perform matrix multiplication.
565581

566582
my @l = $l->value;
567583
my @r = $r->value;
@@ -635,7 +651,7 @@ sub conj { shift->twiddle(@_) }
635651
sub twiddle {
636652
my $self = promote(@_);
637653
my @coords = ();
638-
foreach my $x (@{ $self->data }) { push(@coords, ($x->can("conj") ? $x->conj : $x)) }
654+
for my $x (@{ $self->data }) { push(@coords, ($x->can("conj") ? $x->conj : $x)); }
639655
return $self->make(@coords);
640656
}
641657

@@ -655,6 +671,7 @@ sub transpose {
655671
my @d = $self->dimensions;
656672
if (scalar(@d) == 1) { @d = (1, @d); $self = $self->make($self) }
657673
Value::Error("Can't transpose %d-dimensional matrices", scalar(@d)) unless scalar(@d) == 2;
674+
658675
my @M = ();
659676
my $M = $self->data;
660677
for my $j (0 .. $d[1] - 1) {
@@ -683,8 +700,10 @@ sub I {
683700
my $d = shift;
684701
my $context = shift || $self->context;
685702
$d = ($self->dimensions)[0] if !defined $d && ref($self);
703+
686704
Value::Error("You must provide a dimension for the Identity matrix") unless defined $d;
687705
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
706+
688707
my @M = ();
689708
my $REAL = $context->Package('Real');
690709

@@ -772,14 +791,16 @@ sub E {
772791
($rows, $k, $context) = ($d, $rows, $k);
773792
$d = ($self->dimensions)[0] if ref($self);
774793
}
775-
$context = $self->context unless $context;
776-
Value::Error("You must provide a dimension for an Elementary matrix") unless defined $d;
777-
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
794+
$context = $self->context unless $context;
778795
my @ij = @{$rows};
796+
797+
Value::Error("You must provide a dimension for an Elementary matrix") unless defined $d;
798+
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
779799
Value::Error("Either one or two rows must be specified for an Elementary matrix") unless (@ij == 1 || @ij == 2);
780800
Value::Error(
781801
"If only one row is specified for an Elementary matrix, then a number to scale by must also be specified")
782802
if (@ij == 1 && !defined $k);
803+
783804
for (@ij) {
784805
Value::Error("Row indices must be integers between 1 and $d")
785806
unless ($_ =~ m/^[1-9]\d*$/ && $_ >= 1 && $_ <= $d);
@@ -845,6 +866,7 @@ sub P {
845866
}
846867
my $context = $self->context;
847868
$d = ($self->dimensions)[0] if !defined $d && ref($self) && $self->isSquare;
869+
848870
Value::Error("You must provide a dimension for a Permutation matrix") unless defined $d;
849871
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
850872
for my $c (@cycles) {
@@ -895,6 +917,7 @@ sub Zero {
895917
$n = $m if !defined $n && defined $m;
896918
$m = ($self->dimensions)[0] if !defined $m && ref($self);
897919
$n = ($self->dimensions)[1] if !defined $n && ref($self);
920+
898921
Value::Error("You must provide dimensions for the Zero matrix") unless defined $m && defined $n;
899922
Value::Error("Dimension must be a positive integer") unless $m =~ m/^[1-9]\d*$/ && $n =~ m/^[1-9]\d*$/;
900923
my @M = ();
@@ -933,7 +956,7 @@ Extract a given column from the matrix.
933956
934957
Usage:
935958
936-
my $A1 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
959+
$A1 = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
937960
$A1->column(2); # returns the column Matrix [[2],[6],[10]]
938961
939962
=cut
@@ -962,13 +985,13 @@ Extract an element from the given row/col.
962985
963986
Usage:
964987
965-
my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
988+
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
966989
$A->element(2,3); # returns 7
967990
968-
my $B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]);
991+
$B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]);
969992
$B->element(1,2,1); # returns 3;
970993
971-
my $row = Matrix([4,3,2,1]);
994+
$row = Matrix([4,3,2,1]);
972995
$row->element(2); # returns 3;
973996
=cut
974997

@@ -988,30 +1011,61 @@ Note: this mutates the matrix itself.
9881011
9891012
Usage:
9901013
991-
my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
1014+
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
9921015
$A->setElement([2,3],-5);
9931016
994-
9951017
=cut
9961018

9971019
sub setElement {
9981020
my ($self, $ind, $value) = @_;
9991021

1022+
Value::Error("The index $ind->[0] does not exist in the matrix") unless defined $self->{data}[ $ind->[0] - 1 ];
1023+
10001024
# Drill down into the matrix
1001-
my $el = $self->{data}[ $ind->[0] - 1 ];
1002-
for my $i (1 .. scalar(@$ind) - 1) { $el = $el->{data}[ $ind->[$i] - 1 ]; }
1025+
my $el = \($self->{data}[ $ind->[0] - 1 ]);
1026+
for my $i (1 .. scalar(@$ind) - 1) {
1027+
Value::Error("The index $ind->[$i] does not exist in the matrix") unless defined $$el->{data}[ $ind->[$i] - 1 ];
1028+
$el = \($$el->{data}[ $ind->[$i] - 1 ]);
1029+
}
10031030

10041031
# update the value of $el
1005-
$el = Value::makeValue($value);
1032+
$$el = Value::makeValue($value);
1033+
}
1034+
1035+
# The subroutine extractElements is used in the subMatrix routine. This called recursively to handle
1036+
# any dimension of a Matrix. initially $indices needs to be [] and $elements an arrayref of the
1037+
# elements to be extracted.
1038+
#
1039+
# Through subsequent passes through the subroutine, the indices in the $elements arguments are passed to the $indices.
1040+
1041+
sub extractElements {
1042+
my ($self, $indices, $elements) = @_;
1043+
1044+
# These need to be copies of the array arguments.
1045+
my @ind_copy = @$indices;
1046+
my @elements_copy = @$elements;
1047+
1048+
my $ind = shift @elements_copy;
1049+
push(@ind_copy, [ 1 .. scalar(@$ind) ]);
1050+
1051+
my @M;
1052+
for my $i (@$ind) {
1053+
push(@M,
1054+
ref $self->element($i) eq 'Value::Matrix'
1055+
? $self->element($i)->extractElements(\@ind_copy, \@elements_copy)
1056+
: $self->element($i));
1057+
}
1058+
1059+
return $self->make($self->context, @M);
10061060
}
10071061

10081062
=head3 C<subMatrix>
10091063
10101064
1011-
Return a submatrix of the matrix. If the rows and columns are array refs, the given rows and
1012-
columns of the matrix are returns as a Matrix object.
1065+
Return a submatrix of the matrix. If the indices are array refs, the given rows and
1066+
columns (or more) of the matrix are returns as a Matrix object.
10131067
1014-
If the input are integers, then the submatrix with that row and column removed.
1068+
If the input are integers, then the submatrix with those indices removed.
10151069
10161070
Usage:
10171071
@@ -1022,37 +1076,51 @@ Usage:
10221076
$A->subMatrix(2,3); # returns Matrix([ [ 1, 2, 4 ], [ 9, 10, 12 ] ]);
10231077
10241078
$A->subMatrix([3,1,2],[1,4,2]); # returns Matrix([9,12,10],[1,4,2],[5,8,6]);
1079+
1080+
This subroutine can be used on non 2D matrices. For example,
1081+
1082+
$B = Matrix([2, 4, 6, 8]);
1083+
$B->subMatrix([1, 3]); # returns Matrix([2, 6]);
1084+
$B->subMatrix(2); # returns Matrix([2, 6, 8]);
1085+
1086+
And for 3D matrices:
1087+
1088+
$C = Matrix([ [ [ 1, 2, 3 ], [ 4, 5, 6 ] ], [ [ 7, 8, 9 ], [ 10, 11, 12 ] ] ]);
1089+
$C->subMatrix([1, 2], [1, 2], [1, 3]); # returns Matrix([ [ [ 1, 3 ], [ 4, 6 ] ], [ [ 7, 9 ], [ 10, 12 ] ] ]);
1090+
1091+
$C->subMatrix(1,2,3); # returns Matrix([ [ [ 7, 8 ] ] ]);
1092+
10251093
=cut
10261094

10271095
sub subMatrix {
1028-
my ($self, $r, $c) = @_;
1029-
my $context = $self->context;
1030-
my ($rows, $cols);
1031-
my ($nrow, $ncol) = $self->dimensions;
1032-
1033-
# check if the inputs are integers.
1034-
if (ref $r eq '' && ref $c eq '') {
1035-
Value::Error("The input $r is not a valid row.") unless $r >= 1 && $r <= $nrow && int($r) == $r;
1036-
Value::Error("The input $c is not a valid column.") unless $c >= 1 && $c <= $ncol && int($c) == $c;
1037-
$rows = [ grep { $_ != $r } (1 .. $nrow) ];
1038-
$cols = [ grep { $_ != $c } (1 .. $ncol) ];
1039-
} elsif (ref $r eq 'ARRAY' && ref $c eq 'ARRAY') {
1040-
$rows = $r;
1041-
$cols = $c;
1042-
for my $i (@$rows) {
1043-
Value::Error("The input $i is not a valid row.") unless int($i) == $i && $i >= 1 && $i <= $nrow;
1044-
}
1045-
for my $i (@$cols) {
1046-
Value::Error("The input $i is not a valid column.") unless int($i) == $i && $i >= 1 && $i <= $ncol;
1096+
my ($self, @ind) = @_;
1097+
my @dim = $self->dimensions;
1098+
my @indices; # Indices to keep for submatrix.
1099+
1100+
# check that the input is appropriate for the size of the matrix.
1101+
Value::Error("The indices must be array refs the same size as the dimension of the matrix.") unless $#dim == $#ind;
1102+
1103+
# check that inputs are either all integers or all array refs
1104+
my @index_types = keys %{ { map { ref $_, 1 } @ind } };
1105+
1106+
Value::Error('The inputs must both be integers or array refs.')
1107+
unless scalar(@index_types) == 1 && ($index_types[0] eq '' || $index_types[0] eq 'ARRAY');
1108+
1109+
for my $i (0 .. $#ind) {
1110+
if ($index_types[0] eq '') { # input is a scalar (integer)
1111+
Value::Error("The input $ind[$i] is not a valid index")
1112+
unless $ind[$i] >= 1 && $ind[$i] <= $dim[$i] && int($ind[$i]) == $ind[$i];
1113+
push(@indices, [ grep { $_ != $ind[$i] } (1 .. $dim[$i]) ]);
1114+
1115+
} elsif ($index_types[0] eq 'ARRAY') { # input are array refs
1116+
for my $j (@{ $ind[$i] }) {
1117+
Value::Error("The input $j is not a valid index") unless int($j) == $j && $j >= 1 && $j <= $dim[$i];
1118+
}
1119+
push(@indices, $ind[$i]);
10471120
}
1048-
} else {
1049-
Value::Error('The inputs must both be integers or array refs.');
1050-
}
1051-
my @M = ();
1052-
for my $r (@$rows) {
1053-
push(@M, $self->make($context, map { $self->element($r, $_) } @$cols));
10541121
}
1055-
return $self->make($context, @M);
1122+
1123+
return $self->extractElements([], \@indices);
10561124
}
10571125

10581126
=head3 C<removeRow>
@@ -1072,13 +1140,13 @@ sub removeRow {
10721140
my ($self, $row) = @_;
10731141
my $context = $self->context;
10741142
my @d = $self->dimensions;
1075-
Value::Error("The method removeRow is only valid for 2D matrices.") unless scalar(@d) eq 2;
1143+
Value::Error("The method removeRow is only valid for 2D matrices.") unless scalar(@d) == 2;
10761144
my ($nrow, $ncol) = @d;
10771145
Value::Error("The input $row is not a valid row.")
10781146
unless ref($row) eq '' && $row >= 1 && $row <= $nrow && int($row) == $row;
10791147

10801148
my @M = ();
1081-
for my $r (1 .. $nrow) { push(@M, $self->make($context, $r)) unless $r eq $row; }
1149+
for my $r (1 .. $nrow) { push(@M, $self->row($r)) unless $r eq $row; }
10821150
return $self->make($context, @M);
10831151
}
10841152

@@ -1113,9 +1181,6 @@ sub removeColumn {
11131181
return $self->make($context, @M);
11141182
}
11151183

1116-
# @@@ removeRow, removeColumn @@@
1117-
# @@@ Minor @@@
1118-
11191184
# Convert MathObject Matrix to old-style Matrix
11201185
sub wwMatrix {
11211186
my $self = (ref($_[0]) ? $_[0] : shift);
@@ -1373,4 +1438,3 @@ sub TeX {
13731438
}
13741439

13751440
1;
1376-

0 commit comments

Comments
 (0)