Posts Tagged ‘Perl 6’

Trig Tests

July 14, 2010

Overhauling the trig tests turned out to be much more epic than I had intended. Guess that’s one of the hazards of writing the test generating code in Perl 6 while Rakudo is still under heavy development.

The old test code was very, very thorough in testing each configuration. Basically, we looped through a list of angles, and tested each angle with every possible way it could be called for each type we wanted to test. So say the angle is 45 degrees. Then for the Num type, we’d call 45.Num.sin(Degrees), sin(45.Num, Degrees), and sin(:x(45.Num), Degrees), and then we’d call the same for the equivalent of 45 in Radians, Gradians, and Circles. Then we’d start it all over again using Rat instead of Num. And so on and so forth.

In practice, any implementation is likely to redispatch the trig functions to two basic sets of methods, one for reals and one for complex. So that’s exactly what the tests do now (leaving out the complex case because it is long:

for TrigTest::cosines() -> $angle
    my $desired-result = $angle.result;

    # Num.cos tests -- very thorough
    is_approx($angle.num(Radians).cos, $desired-result, 
              "Num.cos - {$angle.num(Radians)} default");
    for TrigTest::official_bases() -> $base {
        is_approx($angle.num($base).cos($base), $desired-result, 
                  "Num.cos - {$angle.num($base)} $base");

Then, instead of trying to exhaustively test the rest of the cases, we try to generate one test for each case, and leave it at that. For instance, here’s the Rat tests for cos:

# Rat tests
is_approx((-0.785398163404734).Rat(1e-9).cos, 0.707106781186548, "Rat.cos - -0.785398163404734");
is_approx((0).Rat(1e-9).cos(Circles), 1, "Rat.cos(Circles) - 0");
is_approx((0.785398163404734).Rat(1e-9).cos(:base(Radians)), 0.707106781186548, "Rat.cos(:base(Radians)) - 0.785398163404734");
is_approx(cos((1.57079632680947).Rat(1e-9)), 0, "cos(Rat) - 1.57079632680947");
is_approx(cos((135).Rat(1e-9), Degrees), -0.707106781186548, "cos(Rat, Degrees) - 135");
is_approx(cos(:x((3.14159265361894).Rat(1e-9))), -1, "cos(:x(Rat)) - 3.14159265361894");
is_approx(cos(:x((250).Rat(1e-9)), :base(Gradians)), -0.707106781186548, "cos(:x(Rat), :base(Gradians)) - 250");

The use of .Rat(1e-9) is to ensure we have a Rat argument — longer decimal numbers may become Nums otherwise. This tests an additional case (Rat.cos(:base(Radians))) that was never actually tested before. Despite that, the number of Rat tests overall goes from something like 160 to 7.

That’s good, because I added three more types to be tested. Two represent generic Cool types: Str and NotComplex. Str is used to test real numbers in a non-Numeric Cool type. NotComplex is a non-Numeric Cool type which returns a Complex which you call .Numeric on it. The final type, DifferentReal, is a Real type which is not built-in.

Much to my surprise, I actually turned up a couple of bugs in Rakudo’s trig implementation when I tried this testing approach with atan2, the oddball trig function which normally takes two real arguments. First, we were not being generous enough on what the second argument would accept, which was easily corrected. Second, our fake enum standing in for TrigBase looks exactly like an Int to Rakudo. So 2.atan2(Degrees) is indistinguishable from 2.atan2(1), which is definitely not what the user wants. Luckily that’s a pretty silly way to call atan2 anyway.

The process of getting this to work turned up a few Rakudo bugs in the non-trig code, which have been reported. I won’t go into the details here.

By my reckoning, this means there is only one thing left on my list for the numeric grant, srand and rand. I will try to finish them off tomorrow. Thanks to The Perl Foundation and Ian Hague for supporting this work.


April 16, 2010

Now that we are starting to get settled in to our new house, I’ve been meaning to write a progress report on the Numerics grant. This, however, is not that post. Instead it details a quick side project I’ve just done for $work. I don’t reckon I can bill them for this bit, but at least I can get a post out of it, and it definitely is helping with my debugging.

So, I needed to do permutations for a test. The following code is a pretty straightforward port of one of the solutions from HOP:

sub pattern_to_permutation(@pattern, @items1) { 
    my @items = @items1;
    my @r;
    for @pattern {
        push @r, @items.splice($_, 1);

sub n_to_pat($n is copy, $length) { 
    my @odometer; 
    for 1 .. $length -> $i { 
        unshift @odometer, $n % $i; 
        $n div= $i; 
    return $n ?? () !! @odometer; 

sub permute(@items) { 
    my $n = 0;
    gather loop {
        my @pattern = n_to_pat($n++, +@items);
        last unless ?@pattern;
        take pattern_to_permutation(@pattern, @items); 

If you compare it to the original code (which itself is very pretty) on pages 131-134, this version is 14% shorter and I would argue somewhat clearer as well, thanks to the nice features of Perl 6. It also uses Perl 6’s native gather / take construct to make a lazy iterator, rather than needing HOP’s iterator code. (Note that the ugly @items = @items1 hack is a workaround for a Rakudo-bug.)

I am inclined to think that permute might belong in Perl 6’s core. If not, this code shows that it is fairly easy to implement. (That said, I have not come close to exhaustively testing this code.)