256 lines
6.2 KiB
Perl
256 lines
6.2 KiB
Perl
#!/usr/bin/perl
|
|
use strict;
|
|
use warnings;
|
|
use List::Util qw(sum);
|
|
|
|
# Matrix class implemented as array reference
|
|
package Matrix;
|
|
|
|
# Constructor
|
|
sub new {
|
|
my ($class, $data) = @_;
|
|
my $self = defined $data ? $data : [];
|
|
bless $self, $class;
|
|
return $self;
|
|
}
|
|
|
|
# Create matrix from nested blocks
|
|
sub block {
|
|
my ($class, $blocks) = @_;
|
|
my @result;
|
|
|
|
# Get dimensions
|
|
my $num_rows = 0;
|
|
for my $hblock (@$blocks) {
|
|
$num_rows += @{$hblock->[0]};
|
|
}
|
|
|
|
# Process each horizontal block
|
|
my $row_offset = 0;
|
|
for my $hblock (@$blocks) {
|
|
my $block_height = @{$hblock->[0]};
|
|
# Zip and concatenate rows
|
|
for my $i (0..$block_height-1) {
|
|
my @new_row;
|
|
for my $matrix (@$hblock) {
|
|
push @new_row, @{$matrix->[$i]};
|
|
}
|
|
$result[$row_offset + $i] = \@new_row;
|
|
}
|
|
$row_offset += $block_height;
|
|
}
|
|
|
|
return Matrix->new(\@result);
|
|
}
|
|
|
|
# Matrix multiplication (naive)
|
|
sub dot {
|
|
my ($self, $b) = @_;
|
|
my ($rows_a, $cols_a) = $self->shape();
|
|
my ($rows_b, $cols_b) = $b->shape();
|
|
|
|
die "Matrix dimensions don't match for multiplication" unless $cols_a == $rows_b;
|
|
|
|
my @result;
|
|
for my $i (0..$rows_a-1) {
|
|
my @row;
|
|
for my $j (0..$cols_b-1) {
|
|
my $sum = 0;
|
|
for my $k (0..$cols_a-1) {
|
|
$sum += $self->[$i][$k] * $b->[$k][$j];
|
|
}
|
|
push @row, $sum;
|
|
}
|
|
push @result, \@row;
|
|
}
|
|
|
|
return Matrix->new(\@result);
|
|
}
|
|
|
|
# Overload multiplication operator
|
|
use overload '*' => \˙
|
|
|
|
# Matrix addition
|
|
sub add {
|
|
my ($self, $b) = @_;
|
|
my ($rows, $cols) = $self->shape();
|
|
my ($b_rows, $b_cols) = $b->shape();
|
|
|
|
die "Matrix dimensions don't match for addition" unless $rows == $b_rows && $cols == $b_cols;
|
|
|
|
my @result;
|
|
for my $i (0..$rows-1) {
|
|
my @row;
|
|
for my $j (0..$cols-1) {
|
|
push @row, $self->[$i][$j] + $b->[$i][$j];
|
|
}
|
|
push @result, \@row;
|
|
}
|
|
|
|
return Matrix->new(\@result);
|
|
}
|
|
|
|
# Matrix subtraction
|
|
sub subtract {
|
|
my ($self, $b) = @_;
|
|
my ($rows, $cols) = $self->shape();
|
|
my ($b_rows, $b_cols) = $b->shape();
|
|
|
|
die "Matrix dimensions don't match for subtraction" unless $rows == $b_rows && $cols == $b_cols;
|
|
|
|
my @result;
|
|
for my $i (0..$rows-1) {
|
|
my @row;
|
|
for my $j (0..$cols-1) {
|
|
push @row, $self->[$i][$j] - $b->[$i][$j];
|
|
}
|
|
push @result, \@row;
|
|
}
|
|
|
|
return Matrix->new(\@result);
|
|
}
|
|
|
|
# Overload operators
|
|
use overload '+' => \&add;
|
|
use overload '-' => \&subtract;
|
|
|
|
# Strassen's algorithm
|
|
sub strassen {
|
|
my ($self, $b) = @_;
|
|
my ($rows, $cols) = $self->shape();
|
|
my ($b_rows, $b_cols) = $b->shape();
|
|
|
|
die "Matrices must be square" unless $rows == $cols && $b_rows == $b_cols;
|
|
die "Matrices must be the same shape" unless $rows == $b_rows;
|
|
die "Shape must be a power of 2" unless $rows > 0 && ($rows & ($rows - 1)) == 0;
|
|
|
|
if ($rows == 1) {
|
|
return $self->dot($b);
|
|
}
|
|
|
|
my $p = $rows / 2;
|
|
|
|
# Partition matrices
|
|
my $a11 = Matrix->new([map { [ @{$self->[$_]}[0..$p-1] ] } 0..$p-1]);
|
|
my $a12 = Matrix->new([map { [ @{$self->[$_]}[$p..$rows-1] ] } 0..$p-1]);
|
|
my $a21 = Matrix->new([map { [ @{$self->[$_]}[0..$p-1] ] } $p..$rows-1]);
|
|
my $a22 = Matrix->new([map { [ @{$self->[$_]}[$p..$rows-1] ] } $p..$rows-1]);
|
|
|
|
my $b11 = Matrix->new([map { [ @{$b->[$_]}[0..$p-1] ] } 0..$p-1]);
|
|
my $b12 = Matrix->new([map { [ @{$b->[$_]}[$p..$b_cols-1] ] } 0..$p-1]);
|
|
my $b21 = Matrix->new([map { [ @{$b->[$_]}[0..$p-1] ] } $p..$b_rows-1]);
|
|
my $b22 = Matrix->new([map { [ @{$b->[$_]}[$p..$b_cols-1] ] } $p..$b_rows-1]);
|
|
|
|
# Calculate M1..M7
|
|
my $m1 = ($a11 + $a22)->strassen($b11 + $b22);
|
|
my $m2 = ($a21 + $a22)->strassen($b11);
|
|
my $m3 = $a11->strassen($b12 - $b22);
|
|
my $m4 = $a22->strassen($b21 - $b11);
|
|
my $m5 = ($a11 + $a12)->strassen($b22);
|
|
my $m6 = ($a21 - $a11)->strassen($b11 + $b12);
|
|
my $m7 = ($a12 - $a22)->strassen($b21 + $b22);
|
|
|
|
# Calculate C11..C22
|
|
my $c11 = $m1 + $m4 - $m5 + $m7;
|
|
my $c12 = $m3 + $m5;
|
|
my $c21 = $m2 + $m4;
|
|
my $c22 = $m1 - $m2 + $m3 + $m6;
|
|
|
|
return Matrix->block([[$c11, $c12], [$c21, $c22]]);
|
|
}
|
|
|
|
# Round elements
|
|
sub round_matrix {
|
|
my ($self, $ndigits) = @_;
|
|
$ndigits = undef unless defined $ndigits;
|
|
|
|
my @result;
|
|
for my $i (0..$#{$self}) {
|
|
my @row;
|
|
for my $j (0..$#{$self->[$i]}) {
|
|
my $val = $self->[$i][$j];
|
|
if (defined $ndigits) {
|
|
push @row, sprintf("%.${ndigits}f", $val);
|
|
} else {
|
|
push @row, int($val + ($val >= 0 ? 0.5 : -0.5));
|
|
}
|
|
}
|
|
push @result, \@row;
|
|
}
|
|
|
|
return Matrix->new(\@result);
|
|
}
|
|
|
|
# Get matrix shape
|
|
sub shape {
|
|
my ($self) = @_;
|
|
return (0, 0) unless @$self;
|
|
return (scalar @$self, scalar @{$self->[0]});
|
|
}
|
|
|
|
# String representation
|
|
sub stringify {
|
|
my ($self) = @_;
|
|
my @rows;
|
|
for my $row (@$self) {
|
|
push @rows, '[' . join(', ', @$row) . ']';
|
|
}
|
|
return '[' . join(', ', @rows) . ']';
|
|
}
|
|
|
|
use overload '""' => \&stringify;
|
|
|
|
# Examples
|
|
package main;
|
|
|
|
sub examples {
|
|
my $a = Matrix->new([
|
|
[1, 2],
|
|
[3, 4]
|
|
]);
|
|
|
|
my $b = Matrix->new([
|
|
[5, 6],
|
|
[7, 8]
|
|
]);
|
|
|
|
my $c = Matrix->new([
|
|
[1, 1, 1, 1],
|
|
[2, 4, 8, 16],
|
|
[3, 9, 27, 81],
|
|
[4, 16, 64, 256]
|
|
]);
|
|
|
|
my $d = Matrix->new([
|
|
[4, -3, 4/3, -1/4],
|
|
[-13/3, 19/4, -7/3, 11/24],
|
|
[3/2, -2, 7/6, -1/4],
|
|
[-1/6, 1/4, -1/6, 1/24]
|
|
]);
|
|
|
|
my $e = Matrix->new([
|
|
[1, 2, 3, 4],
|
|
[5, 6, 7, 8],
|
|
[9, 10, 11, 12],
|
|
[13, 14, 15, 16]
|
|
]);
|
|
|
|
my $f = Matrix->new([
|
|
[1, 0, 0, 0],
|
|
[0, 1, 0, 0],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1]
|
|
]);
|
|
|
|
print "Naive matrix multiplication:\n";
|
|
print " a * b = " . ($a * $b) . "\n";
|
|
print " c * d = " . $c->round_matrix(0) . "\n";
|
|
print " e * f = " . ($e * $f) . "\n";
|
|
|
|
print "Strassen's matrix multiplication:\n";
|
|
print " a * b = " . $a->strassen($b) . "\n";
|
|
print " c * d = " . $c->strassen($d)->round_matrix(0) . "\n";
|
|
print " e * f = " . $e->strassen($f) . "\n";
|
|
}
|
|
|
|
examples() if __FILE__ eq $0;
|