Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Element-wise comparison with certain precision

Tags:

raku

rakudo

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?

like image 450
Suman Khanal Avatar asked Jul 26 '26 01:07

Suman Khanal


2 Answers

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

like image 107
codesections Avatar answered Jul 28 '26 20:07

codesections


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

like image 33
jubilatious1 Avatar answered Jul 28 '26 22:07

jubilatious1