Element-wise comparison with certain precision

324 views Asked by At

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?

2

There are 2 answers

10
codesections On BEST ANSWER

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.)

1
jubilatious1 On

Try Raku's built-in =~= "tolerance" operator, with the hyper/Z calls you envision.

The =~= "tolerance" operator reads the $*TOLERANCE dynamic variable, which defaults to 1e-15, see link at bottom.

~$ seq 1 16 |  \
   awk '{print sqrt($0)}' |  \
   raku -e  'my @awk = lines;  
             my @raku = (1..16).map: *.sqrt;  \
             .say for [Z=~=] @awk,@raku;'
True
False
False
True
False
False
False
False
True
False
False
False
False
False
False
True

Breaking out the inputs, first awk:

~$ seq 1 16 | awk '{print sqrt($0)}'
1
1.41421
1.73205
2
2.23607
2.44949
2.64575
2.82843
3
3.16228
3.31662
3.4641
3.60555
3.74166
3.87298
4

And Raku's default precision:

~$ raku -e '.sqrt.put for (1..16);'
1
1.4142135623730951
1.7320508075688772
2
2.23606797749979
2.449489742783178
2.6457513110645907
2.8284271247461903
3
3.1622776601683795
3.3166247903554
3.4641016151377544
3.605551275463989
3.7416573867739413
3.872983346207417
4

I recognize the OP requested a tolerance of 1e-12 but maybe the higher tolerance of 1e-15 might prove acceptable? Without seeing the OP's desired output, I can only guess that the [Z=~=] one-liner might be acceptable, with/without an additional .grep call.

[ Re: Tolerance--I don't see a way to set the $*TOLERANCE dynamic variable such that the =~= infix operator reads the new value. Setting $*TOLERANCE = 1e-12 throws an error Cannot modify an immutable Num (1e-15) ].

[ Raku also has a related function, is-approx ].

https://docs.raku.org/routine/%3D~%3D
https://docs.raku.org/routine/is-approx
https://unix.stackexchange.com/a/742940/227738