Skip to content

Commit 427cbfc

Browse files
committed
fuzzy test for row equivalency
1 parent d76f6c5 commit 427cbfc

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
@@ -627,6 +627,101 @@ sub isRREF {
627627
return 1;
628628
}
629629

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