-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathMatrix.pm
More file actions
1616 lines (1299 loc) · 41 KB
/
Matrix.pm
File metadata and controls
1616 lines (1299 loc) · 41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
###########################################################################
#
# Implements the Matrix class.
#
# @@@ Still needs lots of work @@@
=head1 Value::Matrix class
This is the Math Object code for a Matrix.
=head2 References:
=over
=item L<MathObject Matrix methods|https://wiki.openwebwork.org/wiki/Matrix_(MathObject_Class)>
=item L<MathObject Contexts|https://wiki.openwebwork.org/wiki/Common_Contexts>
=item L<CPAN RealMatrix docs|https://search.cpan.org/~leto/Math-MatrixReal-2.09/lib/Math/MatrixReal.pm>
=back
=head2 Matrix-Related libraries and macros:
=over
=item L<MatrixReal1>
=item L<MatrixReduce.pl>
=item L<Matrix>
=item L<MatrixCheckers.pl> -- checking whether vectors form a basis
=item L<MatrixReduce.pl> -- tools for row reduction via elementary matrices
=item L<MatrixUnits.pl> -- Generates unimodular matrices with real entries
=item L<PGmatrixmacros.pl>
=item L<PGmorematrixmacros.pl>
=item L<PGnumericalmacros.pl>
=item L<tableau.pl>
=item L<quickMatrixEntry.pl>
=item L<LinearProgramming.pl>
=back
=head2 Contexts
=over
=item C<Matrix>
Allows students to enter C<[[3,4],[3,6]]>
=item C<Complex-Matrix>
Allows complex entries
=back
=head2 Creation of Matrix MathObjects
Examples:
$M1 = Matrix(1, 2, 3); # degree 1 (row vector)
$M1 = Matrix([1, 2, 3]);
$M2 = Matrix([1, 2], [3, 4]); # degree 2 (matrix)
$M2 = Matrix([[1, 2], [3, 4]]);
$M3 = Matrix([[1, 2], [3, 4]], [[5, 6], [7, 8]]); # degree 3 (tensor)
$M3 = Matrix([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);
$M4 = ...
=head2 Commands added in Value::matrix
=head3 Conversion
$matrix->value produces an array of references to nested arrays, except at
the deepest level, where there will be the more basic MathObjects that make
up the Matrix (e.g. Real, Complex, Fraction, a mix of these, etc)
$M1->value is (1, 2, 3)
$M2->value is ([1, 2], [3, 4])
$M3->value is ([[1, 2], [3, 4]], [[5, 6], [7, 8]])
$matrix->wwMatrix produces CPAN MatrixReal1 matrix, used for computation subroutines and can only
be used on a degree 1 or degree 2 Matrix.
=head3 Information
$matrix->dimensions produces an array. For an n-degree Matrix, the array has n entries for
the n dimensions.
$matrix->degree returns the degree of the Matrix (the depth of the nested array).
=head3 Access values
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) : 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 dimension is length 1
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<element> is passed
k arguments with k < n, then this produces the corresponding degree (n-k) tensor.
=head3 Update values (these need to be added)
see C<change_matrix_entry()> in MatrixReduce and L<https://wiki.openwebwork.org/moodle/mod/forum/discuss.php?d=2970>
=head3 Advanced
$matrix->data: ARRAY reference (internal data) of MathObjects (Real, Complex, Fractions)
stored at each location.
=head2 Passthrough methods
These cover subroutines in C<Matrix.pm> (which overrides or augment CPAN's C<MatrixReal1.pm>).
C<Matrix> is a specialized subclass of C<MatrixReal1.pm>.
The actual calculations for these methods are done in C<pg/lib/Matrix.pm>
trace
proj
proj_coeff
L
R
PL
PR
These cover subroutines in C<pg/lib/MatrixReal1.pm> (this has been modified to handle complex numbers)
The actual calculations are done in C<MatrixReal1.pm> subroutines.
The commands below are C<Value::Matrix> methods unless otherwise noted.
condition
det
inverse
is_symmetric
decompose_LR
dim
norm_one
norm_max
kleene
normalize
solve_LR($v) - LR decomposition
solve($M,$v) - function version of solve_LR
order_LR - order of LR decomposition matrix (number of non-zero equations)(also order() )
order($M) - function version of order_LR
solve_GSM
solve_SSM
solve_RM
=head2 Fractions in Matrices
One can use fractions in Matrices by including C<Context("Fraction")>. For example
Context("Fraction");
$A = Matrix([
[Fraction(1,1), Fraction(1,2), Fraction(1,3)],
[Fraction(1,2), Fraction(1,3), Fraction(1,4)],
[Fraction(1,3), Fraction(1,4), Fraction(1,5)]
]);
and operations will be done using rational arithmetic. Also helpful is the method
C<apply_fraction_to_matrix_entries> in the L<MatrixReduce.pl> macro. Some additional information can be
found at L<https://forums.openwebwork.org/mod/forum/discuss.php?d=2978>.
=head2 methods
=cut
package Value::Matrix;
my $pkg = 'Value::Matrix';
use strict;
use warnings;
no strict "refs";
use Matrix;
use Complex1;
our @ISA = qw(Value);
#
# Convert a value to a matrix. The value can be:
# a list of numbers or list of (nested) references to arrays of numbers,
# a point, vector or matrix object, a matrix-valued formula, or a string
# that evaluates to a matrix
#
sub new { #internal
my $self = shift;
my $class = ref($self) || $self;
my $context = (Value::isContext($_[0]) ? shift : $self->context);
my $M = shift;
$M = [] unless defined $M;
$M = [ $M, @_ ] if scalar(@_) > 0;
$M = @{$M}[0] if ref($M) =~ m/^Matrix(Real1)?/;
$M = Value::makeValue($M, context => $context) if ref($M) ne 'ARRAY';
return bless { data => $M->data, context => $context }, $class
if (Value::classMatch($M, 'Point', 'Vector', 'Matrix') && scalar(@_) == 0);
return $M if Value::isFormula($M) && Value::classMatch($self, $M->type);
my @M = (ref($M) eq 'ARRAY' ? @{$M} : $M);
Value::Error("Matrices must have at least one entry") unless scalar(@M) > 0;
return $self->matrixMatrix($context, @M)
if ref($M[0]) eq 'ARRAY'
|| Value::classMatch($M[0], 'Matrix', 'Vector', 'Point')
|| (Value::isFormula($M[0]) && $M[0]->type =~ m/Matrix|Vector|Point/);
return $self->numberMatrix($context, @M);
}
#
# (Recursively) make a matrix from a list of array refs
# and report errors about the entry types
#
sub matrixMatrix { #internal
my $self = shift;
my $class = ref($self) || $self;
my $context = shift;
my ($x, $m);
my @M = ();
my $isFormula = 0;
for $x (@_) {
if (Value::isFormula($x)) { push(@M, $x); $isFormula = 1 }
else {
$m = $self->new($context, $x);
push(@M, $m);
$isFormula = 1 if Value::isFormula($m);
}
}
my ($type, $len) = ($M[0]->entryType->{name}, $M[0]->length);
for $x (@M) {
Value::Error("Matrix rows must all be the same type")
unless (defined($x->entryType) && $type eq $x->entryType->{name});
Value::Error("Matrix rows must all be the same length") unless ($len eq $x->length);
}
return $self->formula([@M]) if $isFormula;
bless { data => [@M], context => $context }, $class;
}
#
# Form a 1 x n matrix from a list of numbers
# (could become a row of an m x n matrix)
#
sub numberMatrix { #internal
my $self = shift;
my $class = ref($self) || $self;
my $context = shift;
my @M = ();
my $isFormula = 0;
for my $x (@_) {
$x = Value::makeValue($x, context => $context);
Value::Error("Matrix row entries must be numbers: $x ") unless _isNumber($x);
push(@M, $x);
$isFormula = 1 if Value::isFormula($x);
}
return $self->formula([@M]) if $isFormula;
bless { data => [@M], context => $context }, $class;
}
=head3 C<value>
Returns the array of arrayrefs of the matrix.
Usage:
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
$A->value;
# returns ([1,2,3,4],[5,6,7,8],[9,10,11,12])
=cut
sub value {
my $self = shift;
my $M = $self->data;
return @{$M} unless Value::classMatch($M->[0], 'Matrix');
my @M = ();
foreach my $x (@{$M}) { push(@M, [ $x->value ]) }
return @M;
}
=head3 C<dimensions>
Returns the dimensions of the matrix as an array
Usage:
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
$A->dimensions;
returns the array C<(3,4)>
$B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]);
$B->dimensions;
returns C<(2,2,2)>
=cut
#
# Recursively get the dimensions of the matrix.
# Returns (d_1,d_2,...,d_n) for a degree n Matrix.
#
sub dimensions {
my $self = shift;
my $r = $self->length;
my $v = $self->data;
return ($r,) unless Value::classMatch($v->[0], 'Matrix');
return ($r, $v->[0]->dimensions);
}
sub degree {
my $self = shift;
my $v = $self->data;
return 1 unless Value::classMatch($v->[0], 'Matrix');
return 1 + $v->[0]->degree;
}
#
# Return the proper type for the matrix
#
sub typeRef {
my $self = shift;
return Value::Type($self->class, $self->length, $Value::Type{number})
unless Value::classMatch($self->data->[0], 'Matrix');
return Value::Type($self->class, $self->length, $self->data->[0]->typeRef);
}
=head3 C<isSquare>
Return true if the Matrix is degree 1 of length 1 or its last two dimensions are equal, false otherwise
Usage:
$A = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]);
$B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ] ]);
$C = Matrix(1);
$D = Matrix(1, 2);
$E = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ] ]);
$F = Matrix([ [ [ 1, 2 ] ], [ [ 3, 4 ] ] ]);
$A->isSquare; # is 1 (true) because it is a 3x3 matrix
$B->isSquare; # is '' (false) because it is a 2x3 matrix
$C->isSquare; # is 1 (true) because it is a degree 1 matrix of length 1
$D->isSquare; # is '' (false) because it is a degree 1 matrix of length 2
$E->isSquare; # is 1 (true) because it is a 1x2x2 tensor
$F->isSquare; # is '' (false) because it is a 2x1x2 tensor
=cut
sub isSquare {
my $self = shift;
my @d = $self->dimensions;
return $d[0] == 1 if scalar(@d) == 1;
return $d[-1] == $d[-2];
}
=head3 C<isRow>
Return true if the matrix is degree 1 (i.e., is a matrix row)
Usage:
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
$row_vect = Matrix([ 1, 2, 3, 4 ]);
$A->isRow; # is '' (false)
$row_vect->isRow; # is 1 (true)
=cut
sub isRow {
my $self = shift;
return $self->degree == 1;
}
=head3 C<isOne>
Check for identity Matrix (for degree > 2, this means the last two dimensions hold identity matrices)
Usage:
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ], [13, 14, 15, 16] ]);
$A->isOne; # is false
$B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]);
$B->isOne; # is true;
$C = Matrix(1);
$C->isOne; # is true
$D = Matrix([ [ [ 1, 0 ], [ 0, 1 ] ], [ [ 1, 0 ], [ 0, 1 ] ] ]);
$D->isOne; # is true
=cut
sub isOne {
my $self = shift;
return 0 unless $self->isSquare;
if ($self->degree <= 2) {
my $i = 0;
for my $row (@{ $self->{data} }) {
my $j = 0;
for my $k (@{ $row->{data} }) {
return 0 unless $k eq (($i == $j) ? "1" : "0");
$j++;
}
$i++;
}
} else {
for my $row (@{ $self->{data} }) {
return 0 unless $row->isOne;
}
}
return 1;
}
=head3 C<isZero>
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
$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 {
my $self = shift;
for my $x (@{ $self->{data} }) { return 0 unless $x->isZero }
return 1;
}
=head3 C<isUpperTriangular>
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;
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} }) {
return 0 unless $row->isUpperTriangular;
}
}
return 1;
}
=head3 C<isLowerTriangular>
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;
if (scalar(@d) == 1) {
for ((@{ $self->{data} })[ 1 .. $#{ $self->{data} } ]) {
return 0 unless $_ == 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} }) {
return 0 unless $row->isLowerTriangular;
}
}
return 1;
}
=head3 C<isDiagonal>
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<isSymmetric>
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;
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} }) {
return 0 unless $row->isSymmetric;
}
}
return 1;
}
=head3 C<isOrthogonal>
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;
my @d = $self->dimensions;
if (scalar(@d) == 1) {
return 0 unless ($self->{data}->[0] == 1 || $self->{data}->[0] == -1);
}
my $M = $self * $self->transpose;
return $M->isOne;
}
=head3 C<isREF>
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;
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} }) {
return 0 unless $row->isREF;
}
}
return 1;
}
=head3 C<isRREF>
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;
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;
}
}
}
} else {
for my $row (@{ $self->{data} }) {
return 0 unless $row->isREF;
}
}
return 1;
}
sub _isNumber {
my $n = shift;
return Value::isNumber($n) || Value::classMatch($n, 'Fraction');
}
#
# Make arbitrary data into a matrix, if possible
#
sub promote {
my $self = shift;
my $class = ref($self) || $self;
my $context = (Value::isContext($_[0]) ? shift : $self->context);
my $x = (scalar(@_) ? shift : $self);
return $self->new($context, $x, @_) if scalar(@_) > 0 || ref($x) eq 'ARRAY';
$x = Value::makeValue($x, context => $context);
return $x->inContext($context) if ref($x) eq $class;
return $self->make($context, @{ $x->data }) if Value::classMatch($x, 'Point', 'Vector');
Value::Error("Can't convert %s to %s", Value::showClass($x), Value::showClass($self));
}
#
# Don't inherit ColumnVector flag
#
sub noinherit {
my $self = shift;
return ("ColumnVector", "wwM", "lrM", $self->SUPER::noinherit);
}
############################################
#
# Operations on matrices
#
sub add {
my ($self, $l, $r, $other) = Value::checkOpOrderWithPromote(@_);
my @l = @{ $l->data };
my @r = @{ $r->data };
Value::Error("Can't add Matrices with different dimensions")
unless scalar(@l) == scalar(@r);
my @s = ();
for my $i (0 .. scalar(@l) - 1) { push(@s, $l[$i] + $r[$i]) }
return $self->inherit($other)->make(@s);
}
sub sub {
my ($self, $l, $r, $other) = Value::checkOpOrderWithPromote(@_);
my @l = @{ $l->data };
my @r = @{ $r->data };
Value::Error("Can't subtract Matrices with different dimensions")
unless scalar(@l) == scalar(@r);
my @s = ();
for my $i (0 .. scalar(@l) - 1) { push(@s, $l[$i] - $r[$i]) }
return $self->inherit($other)->make(@s);
}
sub mult {
my ($l, $r, $flag) = @_;
my $self = $l;
my $other = $r;
# Perform constant multiplication.
#
if (_isNumber($r)) {
my @coords = ();
for my $x (@{ $l->data }) { push(@coords, $x * $r) }
return $self->make(@coords);
}
# Special case if $r is a Point or Vector and if $l is a degree 2 Matrix,
# we promote $r to a degree 1 Matrix and later restore the product to be Point or Vector
$r =
!$flag
&& Value::classMatch($r, 'Point', 'Vector')
&& $l->degree == 2 ? ($self->promote($r))->transpose : $self->promote($r);
if ($flag) { ($l, $r) = ($r, $l) }
my @dl = $l->dimensions;
my @dr = $r->dimensions;
my @l = $l->value;
my @r = $r->value;
# Special case if $r and $l are both degree 1, perform dot product
if (scalar(@dl) == 1 && scalar(@dr) == 1) {
Value::Error("Can't multiply degree 1 matrices of different lengths") unless ($dl[0] == $dr[0]);
my $result = 0;
for my $i (0 .. $dl[0] - 1) {
$result += $l[$i] * $r[$i];
}
return $result;
}
# Promote degree 1 Matrix to degree 2, as row or column depending on size
# Later restore result to degree 1 if appropriate
my $l1 = scalar(@dl) == 1;
my $r1 = scalar(@dr) == 1;
if ($l1) { @dl = (1, @dl); $l = $self->make($l); @l = $l->value }
if ($r1) { @dr = (@dr, 1); $r = $self->make($r)->transpose; @r = $r->value }
# Multiplication is only possible when dimensions are Zxmxn and Zxnxk
my $bad_dims;
if (scalar(@dl) != scalar(@dr)) {
$bad_dims = 1;
} elsif ($dl[-1] != $dr[-2]) {
$bad_dims = 1;
} else {
for my $i (0 .. scalar(@dl) - 3) {
if ($dl[$i] != $dr[$i]) {
$bad_dims = 1;
last;
}
}
}
Value::Error("Matrices of dimensions %s and %s can't be multiplied", join('x', @dl), join('x', @dr)) if $bad_dims;
# Perform matrix/tensor multiplication.
my @M = ();
for my $i (0 .. $dl[0] - 1) {
if (scalar(@dl) == 2) {
my @row = ();
for my $j (0 .. $dr[1] - 1) {
my $s = 0;
for my $k (0 .. $dl[1] - 1) { $s += $l[$i]->[$k] * $r[$k]->[$j] }
push(@row, $s);
}
push(@M, $self->make(@row));
} else {
push(@M, $l->data->[$i] * $r->data->[$i]);
}
}
$self = $self->inherit($other) if Value::isValue($other);
if ($l1) { return $self->make(@M)->row(1) }
if ($r1) { return $self->make(@M)->transpose->row(1) }
return $other->new($self->make(@M));
}
sub div {
my ($l, $r, $flag) = @_;
my $self = $l;
Value::Error("Can't divide by a Matrix") if $flag;
Value::Error("Matrices can only be divided by Numbers") unless _isNumber($r);
Value::Error("Division by zero") if $r == 0;
my @coords = ();
for my $x (@{ $l->data }) { push(@coords, $x / $r) }
return $self->make(@coords);
}
sub power {
my ($l, $r, $flag) = @_;
my $self = shift;
my $context = $self->context;
Value::Error("Can't use Matrices in exponents") if $flag;
Value::Error("Only square matrices can be raised to a power") unless $l->isSquare;
$r = Value::makeValue($r, context => $context);
if (_isNumber($r) && $r =~ m/^-\d+$/) {
$l = $l->inverse;
$r = -$r;
$self->Error("Matrix is not invertible") unless defined($l);
}
Value::Error("Matrix powers must be non-negative integers") unless _isNumber($r) && $r =~ m/^\d+$/;
return $l if $r == 1;
my @powers = ($l);
my @d = $l->dimensions;
my $d = pop @d;
pop @d;
my $return = $context->Package("Matrix")->I(\@d, $d);
my $p = $r;
while ($p) {
if ($p % 2) {
$p -= 1;
$return *= $powers[-1];
}
push(@powers, $powers[-1] * $powers[-1]);
$p /= 2;
}
return $return;
}
# Do lexicographic comparison (row by row)
sub compare {
my ($self, $l, $r) = Value::checkOpOrderWithPromote(@_);
$l = $l->transpose if ($l->degree == 1 && $r->degree == 2);
$r = $r->transpose if ($l->degree == 2 && $r->degree == 1);
Value::Error("Can't compare Matrices with different dimensions")
unless join(',', $l->dimensions) eq join(',', $r->dimensions);
my @l = @{ $l->data };
my @r = @{ $r->data };
for my $i (0 .. scalar(@l) - 1) {
my $cmp = $l[$i] <=> $r[$i];
return $cmp if $cmp;
}
return 0;
}
sub neg {
my $self = promote(@_);
my @coords = ();
for my $x (@{ $self->data }) { push(@coords, -$x) }
return $self->make(@coords);
}
sub conj { shift->twiddle(@_) }
sub twiddle {
my $self = promote(@_);
my @coords = ();
for my $x (@{ $self->data }) { push(@coords, ($x->can("conj") ? $x->conj : $x)) }
return $self->make(@coords);
}
=head3 C<slice>
Apply this to a degree n Matrix, passing (m, k), and produce the degree (n-1) Matrix defined by
taking all entries whose position has mth index with value k. For example if C<$M> is a 4x5x6
Matrix, then m can be 1, 2, or 3. If m is 2, then k can be 1, 2, 3, 4, or 5. C<$M-<gt>slice(2,3)>
is the 4x6 Matrix such that the entry at position (i,j) is the entry of C<$M> at position (i,3,j).
Usage:
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
$A->slice(1, 2)
# Index 1 identifies the 1st, 2nd, or 3rd row above, and with value 2 we get the second one:
# Matrix([5, 6, 7, 8])
$B = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]);
$B->slice(1, 2)
# Index 1 identifies the two arrays at depth 1, and with value 2 we get the second one:
# Matrix([ [ 5, 6 ], [ 7, 8 ] ])
$B->slice(2, 1)
# Here we take all entries from $B where the 2nd index is 1: the 1 at position (1,1,1),
# the 2 at position (1,1,2), the 5 at position (2,1,1), and the 6 at position (2,1,2):
# Matrix([ [ 1, 2 ], [ 5, 6 ] ])
$B->slice(3, 1)
# Here we take all entries from $B where the 3rd index is 1: the 1 at position (1,1,1),
# the 3 at position (1,2,1), the 5 at position (2,1,1), and the 7 at position (2,2,1):
# Matrix([ [ 1, 3 ], [ 5, 7 ] ])
=cut
sub slice {
my ($self, $index, $value) = @_;
my @d = $self->dimensions;
my $d = scalar(@d);
my $w = $d[0];
Value::Error("index must be an integer from 1 to $d") unless ($index == int($index) && $index >= 1 && $index <= $d);
my $M = $self->data;
if ($index == 1) {
Value::Error("value must be an integer from 1 to $w")
unless ($value == int($value) && $value >= 1 && $value <= $w);
return $M->[ $value - 1 ];
} else {
my @rows;
for (1 .. $w) {
push @rows, $M->[ $_ - 1 ]->slice($index - 1, $value);
}
return $self->make(@rows);
}
}
=head3 C<transpose>
Take the transpose of a matrix. For a degree 1 Matrix, first promote to a degree 2 Matrix.
For a degree n Matrix, apply a permutation of the indices. The default permutation transposes the
last two indices. To specify a permutation, provide an array reference representing a cycle
or an array of array references that represents a product of cycles. If a permutation is not
specified, the default is the usual transposition of the last two indices.
Usage:
$A = Matrix([
[ 1, 2, 3, 4 ],
[ 5, 6, 7, 8 ],
[ 9, 10, 11, 12 ]
]);
$A->transpose
# will be
# [
# [ 1, 5, 9 ],
# [ 2, 6, 10 ],
# [ 3, 7, 11 ],
# [ 4, 8, 12 ]
# ]
$B = Matrix([
[ [ 1, 2 ], [ 3, 4 ] ],
[ [ 5, 6 ], [ 7, 8 ] ]
]);
$B->transpose([1, 2, 3])
# will be
# [
# [ [ 1, 3 ], [ 5, 7 ] ],
# [ [ 2, 4 ], [ 6, 8 ] ]
# ]
$C = Matrix([
[ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ],
[ [ [ 9, 10 ], [ 11, 12 ] ], [ [ 13, 14 ], [ 15, 16 ] ] ]
]);
$C->transpose([ [ 1, 2], [3, 4] ])
# will be
# [
# [ [ [ 1, 3 ], [ 2, 4 ] ], [ [ 9, 11 ], [ 10, 12 ] ] ],
# [ [ [ 5, 7 ], [ 6, 8 ] ], [ [ 13, 15 ], [ 14, 16 ] ] ]
# ]
=cut
sub transpose {
my $self = shift;
my $p = shift;
my @d = $self->dimensions;
my $N = scalar(@d);
# elevate a degree 1 Matrix to degree 2
if ($N == 1) { @d = (1, @d); $N = 2; $self = $self->make($self) }
# default to transpose last two indices
$p = [ [ $N - 1, $N ] ] unless $p;
# build the permutation hash from cycles
my %p;
if (ref $p eq 'HASH') {
%p = %{$p};
} else {
$p = [$p] unless ref($p->[0]);
my @p = (1 .. $N);
for my $cycle (@{$p}) {
next unless defined $cycle->[0];
my $tmp = $p[ $cycle->[0] - 1 ];
for my $i (0 .. $#{$cycle} - 1) {
$p[ $cycle->[$i] - 1 ] = $p[ $cycle->[ $i + 1 ] - 1 ];
}
$p[ $cycle->[-1] - 1 ] = $tmp;
}
%p = map { $_ => $p[ $_ - 1 ] } (1 .. $N);
}
%p = reverse %p;
my @M = ();
if ($N == 2) {
return $self if ($p{1} == 1);
my $M = $self->data;
for my $j (0 .. $d[1] - 1) {
my @row = ();
for my $i (0 .. $d[0] - 1) { push(@row, $M->[$i]->data->[$j]) }
push(@M, $self->make(@row));
}
} else {
# reduce the permutation hash
my @q = map { $p{$_} } (1 .. $N);
my $p1 = shift @q;
for (@q) {
$_-- if ($_ >= $p1);
}
my %q = map { $_ => $q[ $_ - 1 ] } (1 .. $N - 1);
for my $j (1 .. $d[ $p1 - 1 ]) {
my $slice = $self->slice($p1, $j);
push(@M, $slice->class eq 'Matrix' ? $slice->transpose(\%q) : $slice);
}
}
return $self->make(@M);
}
=head3 C<I>
Get an identity Matrix of the requested size
Value::Matrix->I(n)