Search code examples
rakurakudo

Element-wise comparison with certain precision


I am looking for testing output of my function (which returns array) with the expected output array.

Example:

use Test;
sub myoutput($n) {
    (1..$n)>>.sqrt
}

is myoutput(3), (1, 1.4142135623730951, 1.7320508075688772);

This looks fine but I want to set precision to 1e-12.

What I came out with is this:

sub element_wise_testing_with_precision(@functionoutput, @expectedoutput, $precision) {
    die "Provide equal elements!" if +@functionoutput != +@expectedoutput;
    loop (my $i = 0; $i < +@functionoutput; $i++)  {
        is-approx @functionoutput[$i], @expectedoutput[$i], $precision;
    }
}


my @expected = (1, 1.4142135623730951, 1.7320508075688772);
element_wise_testing_with_precision(myoutput(3), @expected, 1e-12)

Works (!) but not sure if it is the right way. Are there approaches to do this thing using Z operator or Hyper operator as they appear to do element-wise operation?


Solution

  • Your instincts about hyper and zip/the Z operator are exactly correct: it's possible to do what you want with either of them. The missing piece of the puzzle is partial application (which Raku often calls priming). Raku offers two ways to prime – that is, partially apply – a function: the .assuming method (to prime a subroutine) and Whatever-priming (to prime an operator or method).

    Since &is-approx is a subroutine, &assuming is the priming method we're after. To specify precision as the third argument, we'd write &is-approx.assuming(*, *, $precision). Or, since &is-approx allows specifying precision with a named argument, we can simplify that to &is-approx.assuming: :abs-tol($precision).

    Once we've done that, we can apply our new function element wise using the hyper or Z metaoperators. Note that, because metaoperators expects an infix operator, we'll need to use the infix form of our function by wrapping our function in square brackets.

    Here's your code with those minimal changes:

    sub element_wise_testing_with_precision(@functionoutput, @expectedoutput, $precision) {
        die "Provide equal elements!" if +@functionoutput != +@expectedoutput;
    
        my &close-enough = &is-approx.assuming(*,*, $precision);
        @functionoutput «[&close-enough]» @expectedoutput
    
      # or this also works:
      # @functionoutput Z[&close-enough] @expectedoutput
    }
    

    Here's a version with some tangential changes to make it a bit more idiomatic:

    sub element-wise-is-approx(@got, @expected, $abs-tol) {
        PRE { +@got == +@expected }
    
        my &close-enough = &is-approx.assuming: :$abs-tol;
        @got «[&close-enough]» @expected
    }
    
    element-wise-is-approx(myoutput(3), @expected, 1e-12);
    

    Indeed, we could even do this inline, as shown below. Here, we switch to the non-DWIM version of hyper (» «), to enforce equal-sized arguments.

    myoutput(3) »[&(&is-approx.assuming(*,*,1e-12))]« @expected
    

    (Note the extra &( ) in in the infix operator is required to clarify our intent to the Raku compiler.)