Skip to content

Commit a7e2649

Browse files
committed
tensor multiplication, powers, idenity, inverses
1 parent 36e2645 commit a7e2649

File tree

1 file changed

+174
-65
lines changed

1 file changed

+174
-65
lines changed

lib/Value/Matrix.pm

+174-65
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,17 @@ Allows complex entries
6464
6565
=back
6666
67-
=head2 Creation of Matrices
67+
=head2 Creation of Matrix MathObjects
6868
6969
Examples:
7070
71-
$M1 = Matrix(1, 2, 3); # 1D (row vector)
71+
$M1 = Matrix(1, 2, 3); # degree 1 (row vector)
7272
$M1 = Matrix([1, 2, 3]);
7373
74-
$M2 = Matrix([1, 2], [3, 4]); # 2D (matrix)
74+
$M2 = Matrix([1, 2], [3, 4]); # degree 2 (matrix)
7575
$M2 = Matrix([[1, 2], [3, 4]]);
7676
77-
$M3 = Matrix([[1, 2], [3, 4]], [[5, 6], [7, 8]]); # 3D (tensor)
77+
$M3 = Matrix([[1, 2], [3, 4]], [[5, 6], [7, 8]]); # degree 3 (tensor)
7878
$M3 = Matrix([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);
7979
8080
$M4 = ...
@@ -83,21 +83,23 @@ Examples:
8383
8484
=head3 Conversion
8585
86-
$matrix->value produces an array of numbers (for a 1D tensor) or array refs representing the rows.
86+
$matrix->value produces an array of numbers (for a degree 1 Matrix) or array refs representing the rows.
8787
These are perl numbers and array refs, not MathObjects.
8888
8989
$M1->value is (1, 2, 3)
9090
$M2->value is ([1, 2], [3, 4])
9191
$M3->value is ([[1, 2], [3, 4]], [[5, 6], [7, 8]])
9292
9393
$matrix->wwMatrix produces CPAN MatrixReal1 matrix, used for computation subroutines and can only
94-
be used on 1D tensors (row vector) or 2D tensors (matrix).
94+
be used on a degree 1 or degree 2 Matrix.
9595
9696
=head3 Information
9797
98-
$matrix->dimensions produces an array. For an n-dimensional tensor, the array has n entries for
98+
$matrix->dimensions produces an array. For an n-degree Matrix, the array has n entries for
9999
the n dimensions.
100100
101+
$matrix->degree returns the degree of the Matrix (the depth of the nested array).
102+
101103
=head3 Access values
102104
103105
row(i) : MathObjectMatrix
@@ -305,7 +307,7 @@ returns C<(2,2,2)>
305307

306308
#
307309
# Recursively get the dimensions of the matrix.
308-
# Returns (d_1,d_2,...,d_n) for an nD matrix.
310+
# Returns (d_1,d_2,...,d_n) for an degree n Matrix.
309311
#
310312
sub dimensions {
311313
my $self = shift;
@@ -315,6 +317,13 @@ sub dimensions {
315317
return ($r, $v->[0]->dimensions);
316318
}
317319

320+
sub degree {
321+
my $self = shift;
322+
my $v = $self->data;
323+
return 1 unless Value::classMatch($v->[0], 'Matrix');
324+
return 1 + $v->[0]->degree;
325+
}
326+
318327
#
319328
# Return the proper type for the matrix
320329
#
@@ -327,24 +336,31 @@ sub typeRef {
327336

328337
=head3 C<isSquare>
329338
330-
Return true is the matrix is 1D of length 1 or 2D and square, false otherwise
339+
Return true if the Matrix is degree 1 of length 1 or its last two dimensions are equal, false otherwise
331340
332341
Usage:
333342
334-
$A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
335-
$B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]);
343+
$A = Matrix([ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]);
344+
$B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ] ]);
345+
$C = Matrix(1);
346+
$D = Matrix(1, 2);
347+
$E = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ] ]);
348+
$F = Matrix([ [ [ 1, 2 ] ], [ [ 3, 4 ] ] ]);
336349
337-
$A->isSquare; # is '' (false)
338-
$B->isSquare; # is 1 (true);
350+
$A->isSquare; # is 1 (true) because it is a 3x3 matrix
351+
$B->isSquare; # is '' (false) because it is a 2x3 matrix
352+
$C->isSquare; # is 1 (true) because it is a degree 1 matrix of length 1
353+
$D->isSquare; # is '' (false) because it is a degree 1 matrix of length 2
354+
$E->isSquare; # is 1 (true) because it is a 1x2x2 tensor
355+
$F->isSquare; # is '' (false) because it is a 2x1x2 tensor
339356
340357
=cut
341358

342359
sub isSquare {
343360
my $self = shift;
344361
my @d = $self->dimensions;
345-
return 1 if scalar(@d) == 1 && $d[0] == 1;
346-
return 0 if scalar(@d) != 2;
347-
return $d[0] == $d[1];
362+
if (scalar(@d) == 1) { return $d[0] == 1 }
363+
return ($d[-1] == $d[-2]);
348364
}
349365

350366
=head3 C<isRow>
@@ -369,7 +385,7 @@ sub isRow {
369385

370386
=head3 C<isOne>
371387
372-
Check for identity matrix.
388+
Check for identity Matrix (for degree > 2, this means the last two dimensions hold identity matrices)
373389
374390
Usage:
375391
@@ -379,19 +395,33 @@ Usage:
379395
$B = Matrix([ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]);
380396
$B->isOne; # is true;
381397
398+
$C = Matrix(1);
399+
$C->isOne; # is true
400+
401+
$D = Matrix([ [ [ 1, 0 ], [ 0, 1 ] ], [ [ 1, 0 ], [ 0, 1 ] ] ]);
402+
$D->isOne; # is true
403+
382404
=cut
383405

384406
sub isOne {
385407
my $self = shift;
386408
return 0 unless $self->isSquare;
387-
my $i = 0;
388-
for my $row (@{ $self->{data} }) {
389-
my $j = 0;
390-
for my $k (@{ $row->{data} }) {
391-
return 0 unless $k eq (($i == $j) ? "1" : "0");
392-
$j++;
409+
if ($self->degree <= 2) {
410+
my $i = 0;
411+
for my $row (@{ $self->{data} }) {
412+
my $j = 0;
413+
for my $k (@{ $row->{data} }) {
414+
return 0 unless $k eq (($i == $j) ? "1" : "0");
415+
$j++;
416+
}
417+
$i++;
418+
}
419+
} else {
420+
for my $row (@{ $self->{data} }) {
421+
if (!$row->isOne) {
422+
return 0;
423+
}
393424
}
394-
$i++;
395425
}
396426
return 1;
397427
}
@@ -597,36 +627,73 @@ sub mult {
597627
return $self->make(@coords);
598628
}
599629

600-
# Make points and vectors into columns if they are on the right.
601-
$r = !$flag && Value::classMatch($r, 'Point', 'Vector') ? ($self->promote($r))->transpose : $self->promote($r);
630+
# Special case if $r is a Point or Vector and if $l is a degree 2 Matrix,
631+
# we promote $r to a degree 1 Matrix and later restore the product to be Point or Vector
632+
$r =
633+
!$flag
634+
&& Value::classMatch($r, 'Point', 'Vector')
635+
&& $l->degree == 2 ? ($self->promote($r))->transpose : $self->promote($r);
602636

603-
if (!$flag && Value::classMatch($r, 'Point', 'Vector')) { $r = ($self->promote($r))->transpose }
604-
else { $r = $self->promote($r) }
605-
#
606-
if ($flag) { my $tmp = $l; $l = $r; $r = $tmp }
637+
if ($flag) { ($l, $r) = ($r, $l) }
607638
my @dl = $l->dimensions;
608639
my @dr = $r->dimensions;
609-
if (scalar(@dl) == 1) { @dl = (1, @dl); $l = $self->make($l) }
610-
if (scalar(@dr) == 1) { @dr = (@dr, 1); $r = $self->make($r)->transpose }
611-
Value::Error("Can only multiply 2-dimensional matrices") if scalar(@dl) > 2 || scalar(@dr) > 2;
612-
Value::Error("Matrices of dimensions %dx%d and %dx%d can't be multiplied", @dl, @dr) unless ($dl[1] == $dr[0]);
640+
my @l = $l->value;
641+
my @r = $r->value;
642+
643+
# Special case if $r and $l are both degree 1, perform dot product
644+
if (scalar(@dl) == 1 && scalar(@dr) == 1) {
645+
Value::Error("Can't multiply degree 1 matrices of different lengths") unless ($dl[0] == $dr[0]);
646+
my $result = 0;
647+
for my $i (0 .. $dl[0] - 1) {
648+
$result += $l[$i] * $r[$i];
649+
}
650+
return $result;
651+
}
613652

614-
# Perform matrix multiplication.
653+
# Promote degree 1 Matrix to degree 2, as row or column depending on side
654+
# Later restore result to degree 1 if appropriate
655+
my $l1 = scalar(@dl) == 1;
656+
my $r1 = scalar(@dr) == 1;
657+
if ($l1) { @dl = (1, @dl); $l = $self->make($l); @l = $l->value }
658+
if ($r1) { @dr = (@dr, 1); $r = $self->make($r)->transpose; @r = $r->value }
659+
660+
# Multiplication is only possible when dimensions are Zxmxn and Zxnxk
661+
my $bad_dims;
662+
if (scalar(@dl) != scalar(@dr)) {
663+
$bad_dims = 1;
664+
} elsif ($dl[-1] != $dr[-2]) {
665+
$bad_dims = 1;
666+
} else {
667+
for my $i (0 .. scalar(@dl) - 3) {
668+
if ($dl[$i] != $dr[$i]) {
669+
$bad_dims = 1;
670+
last;
671+
}
672+
}
673+
}
674+
Value::Error("Matrices of dimensions %s and %s can't be multiplied", join('x', @dl), join('x', @dr)) if $bad_dims;
615675

616-
my @l = $l->value;
617-
my @r = $r->value;
676+
# Perform matrix/tensor multiplication.
618677
my @M = ();
619678
for my $i (0 .. $dl[0] - 1) {
620-
my @row = ();
621-
for my $j (0 .. $dr[1] - 1) {
622-
my $s = 0;
623-
for my $k (0 .. $dl[1] - 1) { $s += $l[$i]->[$k] * $r[$k]->[$j] }
624-
push(@row, $s);
679+
if (scalar(@dl) == 2) {
680+
my @row = ();
681+
for my $j (0 .. $dr[1] - 1) {
682+
my $s = 0;
683+
for my $k (0 .. $dl[1] - 1) { $s += $l[$i]->[$k] * $r[$k]->[$j] }
684+
push(@row, $s);
685+
}
686+
push(@M, $self->make(@row));
687+
} else {
688+
push(@M, $l->data->[$i] * $r->data->[$i]);
625689
}
626-
push(@M, $self->make(@row));
627690
}
628691
$self = $self->inherit($other) if Value::isValue($other);
629-
return $self->make(@M);
692+
693+
if ($l1) { return $self->make(@M)->row(1) }
694+
if ($r1) { return $self->make(@M)->transpose->row(1) }
695+
696+
return $other->new($self->make(@M));
630697
}
631698

632699
sub div {
@@ -653,10 +720,23 @@ sub power {
653720
$self->Error("Matrix is not invertible") unless defined($l);
654721
}
655722
Value::Error("Matrix powers must be non-negative integers") unless _isNumber($r) && $r =~ m/^\d+$/;
656-
return $context->Package("Matrix")->I($l->length, $context) if $r == 0;
657-
my $M = $l;
658-
for my $i (2 .. $r) { $M = $M * $l }
659-
return $M;
723+
return $l if $r == 1;
724+
my @powers = ($l);
725+
my @d = $l->dimensions;
726+
my $d = pop @d;
727+
pop @d;
728+
my $return = $context->Package("Matrix")->I(\@d, $d);
729+
my $p = $r;
730+
731+
while ($p) {
732+
if ($p % 2) {
733+
$p -= 1;
734+
$return *= $powers[-1];
735+
}
736+
push(@powers, $powers[-1] * $powers[-1]);
737+
$p /= 2;
738+
}
739+
return $return;
660740
}
661741

662742
# Do lexicographic comparison (row by row)
@@ -718,31 +798,51 @@ sub transpose {
718798

719799
=head3 C<I>
720800
721-
Get an identity matrix of the requested size
801+
Get a identity Matrix of the requested size
722802
723803
Value::Matrix->I(n)
724804
725805
Usage:
726806
727-
Value::Matrix->I(3); # returns a 3 by 3 identity matrix.
728-
$A->I; # return an n by n identity matrix, where n is the number of rows of A
807+
Value::Matrix->I(3); # returns a 3x3 identity matrix.
808+
Value::Matrix->I([2],3); # returns a 2x3x3 identity Matrix (tensor)
809+
Value::Matrix->I([2,4],2); # returns a 2x4x2x2 identity Matrix (tensor)
810+
$A->I; # return an identity matrix of the appropriate degree and dimensions such that I*A = A
729811
730812
=cut
731813

732814
sub I {
733-
my $self = shift;
734-
my $d = shift;
735-
my $context = shift || $self->context;
736-
$d = ($self->dimensions)[0] if !defined $d && ref($self);
737-
738-
Value::Error("You must provide a dimension for the Identity matrix") unless defined $d;
739-
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
815+
my $self = shift;
816+
my @d;
817+
my $d;
818+
my $context;
819+
if (ref($self) eq 'Value::Matrix') {
820+
@d = $self->dimensions;
821+
pop @d unless scalar(@d) == 1;
822+
$d = pop @d;
823+
$context = $self->context;
824+
} else {
825+
$d = shift; # array ref, or number
826+
if (ref($d) eq 'ARRAY') {
827+
@d = @{$d};
828+
$d = shift;
829+
}
830+
Value::Error("You must provide a dimension for the Identity matrix") unless ($d || $d eq '0');
831+
Value::Error("Dimension must be a positive integer") unless $d =~ m/^[1-9]\d*$/;
832+
$context = shift || $self->context;
833+
}
740834

741-
my @M = ();
835+
my @M;
742836
my $REAL = $context->Package('Real');
743837

744-
for my $i (0 .. $d - 1) {
745-
push(@M, $self->make($context, map { $REAL->new(($_ == $i) ? 1 : 0) } 0 .. $d - 1));
838+
if (!@d) {
839+
for my $i (0 .. $d - 1) {
840+
push(@M, $self->make($context, map { $REAL->new(($_ == $i) ? 1 : 0) } 0 .. $d - 1));
841+
}
842+
} else {
843+
for my $i (1 .. $d[0]) {
844+
push(@M, Value::Matrix->I([ @d[ 1 .. $#d ] ], $d));
845+
}
746846
}
747847
return $self->make($context, @M);
748848
}
@@ -1108,10 +1208,19 @@ sub det {
11081208

11091209
sub inverse {
11101210
my $self = shift;
1111-
$self->wwMatrixLR;
1112-
Value->Error("Can't take inverse of non-square matrix") unless $self->isSquare;
1113-
my $I = $self->{lrM}->invert_LR;
1114-
return (defined($I) ? $self->new($I) : $I);
1211+
my @d = $self->dimensions;
1212+
my @M = ();
1213+
for my $i (0 .. $d[0] - 1) {
1214+
if (scalar(@d) == 2) {
1215+
$self->wwMatrixLR;
1216+
Value->Error("Can't take inverse of non-square matrix") unless $self->isSquare;
1217+
my $I = $self->{lrM}->invert_LR;
1218+
return (defined($I) ? $self->new($I) : $I);
1219+
} else {
1220+
push(@M, $self->data->[$i]->inverse);
1221+
}
1222+
}
1223+
return $self->new($self->make(@M));
11151224
}
11161225

11171226
sub decompose_LR {

0 commit comments

Comments
 (0)