Skip to content

Commit 38dcb13

Browse files
committed
fuzzy test for row equivalency
1 parent 681249b commit 38dcb13

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

lib/Value/Matrix.pm

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,101 @@ sub isRREF {
631631
return 1;
632632
}
633633

634+
=head3 C<isREQ>
635+
636+
Fuzzy, probabilistic check for if two matrices are row equivalent.
637+
638+
Usage:
639+
640+
$A = Matrix([3, 1], [1, 0.3333]);
641+
$B = Matrix([1, 1/3], [0, 0]);
642+
$A->isREQ($B); # true
643+
644+
=cut
645+
646+
sub isREQ {
647+
my ($l, $r) = @_;
648+
my @ldim = $l->dimensions;
649+
my @rdim = $r->dimensions;
650+
Value::Error('Cannot compare row equivalency of matrices of different degree') unless scalar @ldim == scalar @rdim;
651+
for my $i (0 .. $#ldim) {
652+
Value::Error('Cannot compare row equivalency because matrices differ in the '
653+
. $l->NameForNumber($i + 1)
654+
. ' dimension')
655+
unless $ldim[$i] == $rdim[$i];
656+
}
657+
return 1 if $l->isZero && $r->isZero;
658+
return 0 if $l->isZero && !$r->isZero || !$l->isZero && $r->isZero;
659+
660+
if (scalar @ldim == 1) {
661+
# simply need to determine if rows are parallel
662+
my ($lk, $rk);
663+
for my $i (1 .. $ldim[0]) {
664+
if ($l->element($i) != 0) {
665+
$lk = $l->element($i);
666+
$rk = $r->element($i);
667+
last;
668+
}
669+
}
670+
return $lk * $r == $rk * $l;
671+
} elsif (scalar @ldim == 2) {
672+
# get Gram-Schmidt basis for each row space
673+
my @rrows = map { $r->new($_) } $r->value;
674+
@rrows = grep { !$_->isZero } @rrows;
675+
@rrows = map { $_ / ($_ * $_)**0.5 } @rrows;
676+
my @rgs = ($rrows[0]);
677+
for my $i (1 .. $#rrows) {
678+
my $proj = $r->new((0) x $rdim[1]);
679+
for my $v (@rgs) {
680+
$proj += ($rrows[$i] * $v) * $v;
681+
}
682+
my $gs = $rrows[$i] - $proj;
683+
push @rgs, $gs / ($gs * $gs)**0.5 unless $rrows[$i] == $proj;
684+
}
685+
my @lrows = map { $l->new($_) } $l->value;
686+
@lrows = grep { !$_->isZero } @lrows;
687+
@lrows = map { $_ / ($_ * $_)**0.5 } @lrows;
688+
my @lgs = ($lrows[0]);
689+
for my $i (1 .. $#lrows) {
690+
my $proj = $r->new((0) x $ldim[1]);
691+
for my $v (@lgs) {
692+
$proj += ($lrows[$i] * $v) * $v;
693+
}
694+
my $gs = $lrows[$i] - $proj;
695+
push @lgs, $gs / ($gs * $gs)**0.5 unless $lrows[$i] == $proj;
696+
}
697+
698+
return 0 if scalar @lgs != scalar @rgs;
699+
700+
# project each row from $rrows onto @lgs;
701+
# if the complement is nonzero, the row spaces disagree
702+
for my $v (@rrows) {
703+
my $proj = $r->new((0) x $ldim[1]);
704+
for my $w (@lgs) {
705+
$proj += ($v * $w) * $w;
706+
}
707+
return 0 unless $v == $proj;
708+
}
709+
# and vice versa
710+
for my $v (@lrows) {
711+
my $proj = $r->new((0) x $rdim[1]);
712+
for my $w (@rgs) {
713+
$proj += ($v * $w) * $w;
714+
}
715+
return 0 unless $v == $proj;
716+
}
717+
718+
# if we got this far, the row spaces are (fuzzy) equal
719+
# so the matrices are row equivalent
720+
return 1;
721+
} else {
722+
for my $i (1 .. $ldim[0]) {
723+
return 0 unless $l->new($l->{data}[ $i - 1 ])->isREQ($r->new($r->{data}[ $i - 1 ]));
724+
}
725+
return 1;
726+
}
727+
}
728+
634729
sub _isNumber {
635730
my $n = shift;
636731
return Value::isNumber($n) || Value::classMatch($n, 'Fraction');

t/math_objects/matrix.t

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,50 @@ subtest 'Test if Matrix is in (R)REF' => sub {
180180
ok !$B4->isRREF, "$B4 is not in RREF";
181181
};
182182

183+
subtest 'Check if two matrices are (fuzzy) row equivalent' => sub {
184+
my $A1 = Matrix(1, 2, 3);
185+
my $A2 = Matrix([ 1, 2, 3 ], [ 4, 5, 6 ]);
186+
my $A3 = Matrix([ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ]);
187+
my $B1 = Matrix(2, 4, 6);
188+
my $B2 = Matrix([ 1, 2 ], [ 3, 4 ], [ 5, 6 ]);
189+
my $C1 = Matrix(0, 4, 6);
190+
my $Z1 = Matrix([ 0, 0, 0 ], [ 0, 0, 0 ]);
191+
my $Z2 = Matrix([ 0, 0, 0 ], [ 0, 0, 10**-14 ]);
192+
193+
my $M1 = Matrix([ 3, 1 ], [ 1, 1 / 3 ]);
194+
my $M2 = Matrix([ 3, 1 ], [ 0, 0 ]);
195+
my $M3 = Matrix([ 1, 0.3333 ], [ 0, 0 ]);
196+
my $M4 = Matrix([ 1, 0.33 ], [ 0, 0 ]);
197+
my $M5 = Matrix([ 6, 2 ], [ 9, 3 ]);
198+
my $M6 = Matrix([ 3, 1 ], [ 1, 3 ]);
199+
200+
my $N1 = Matrix([ [ 1, 2 ], [ 2, 4 ] ], [ [ 1, 2 ], [ 3, 4 ] ]);
201+
my $N2 = Matrix([ [ 3, 6 ], [ 0, 0 ] ], [ [ 1, 0 ], [ 0, 1 ] ]);
202+
my $N3 = Matrix([ [ 3, 6 ], [ 1, 0 ] ], [ [ 1, 0 ], [ 0, 1 ] ]);
203+
204+
ok $Z1->isREQ($Z2), 'Zero matrices are row equivalent';
205+
ok !$Z1->isREQ($A2), 'A zero matrix is not row equivalent to a nonzero matrix';
206+
ok $A1->isREQ($B1), 'Parallel degree 1 matrices are row equivalent';
207+
ok !$C1->isREQ($B1), 'Non-parallel degree 1 matrices are not row equivalent';
208+
ok $M2->isREQ($M5), 'Row equivalent matrices are row equivalent';
209+
ok $M2->isREQ($M1), 'Row equivalent matrices are row equivalent, even with some machine rounding';
210+
ok $M2->isREQ($M3), 'Row equivalent matrices are row equivalent, even with student rounding';
211+
ok !$M2->isREQ($M4), 'Matrices are not row equivalent if rounding is too much';
212+
ok !$M2->isREQ($M6), 'Non-row equivalent matrices are not row equivalent';
213+
ok $N1->isREQ($N2), 'Row equivalent matrices are row equivalent, even at degree above 2';
214+
ok !$N1->isREQ($N3), 'Non-row equivalent matrices are not row equivalent, even at degree above 2';
215+
216+
like dies {
217+
$A2->isREQ($A3);
218+
}, qr/Cannot compare row equivalency of matrices of different degree/,
219+
'Test that error is thrown for comparing matrices of differing degrees';
220+
like dies {
221+
$A2->isREQ($B2);
222+
}, qr/Cannot compare row equivalency because matrices differ in the first dimension/,
223+
'Test that error is thrown for comparing matrices of differing dimensioon';
224+
225+
};
226+
183227
subtest 'Transpose a Matrix' => sub {
184228
my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]);
185229
my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]);

0 commit comments

Comments
 (0)