Skip to content

Conversation

@rreusser
Copy link
Member

@rreusser rreusser commented Aug 23, 2017

Resolves #100.

Checklist

Please ensure the following tasks are completed before submitting this pull request.

  • Read, understood, and followed the contributing guidelines, including the relevant style guides.
  • Read and understand the Code of Conduct.
  • Read and understood the licensing terms.
  • Searched for existing issues and pull requests before submitting this pull request.
  • Filed an issue (or an issue already existed) prior to submitting this pull request.
  • Rebased onto latest develop.
  • Submitted against develop branch.

Description

What is the purpose of this pull request?

This pull request:

  • merges the implementations of sin and cos to compute the two simultaneously. The only tricky spot was that the tolerances for early bailout in the sin/cos kernels were 2^-26 vs 2^-27. I opted for the larger of the two (2^-26) so that the other might require a bit of extra computation, but since it needs to do the reduction and call the actual kernel anyway, it didn't seem like a big deal.

Related Issues

Does this pull request have any related issues?

No.

Questions

Any questions for reviewers of this pull request?

No.

Other

Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes.

The benchmarks are interesting. On the strictly js side, there are quite a few comparisons to be done. In short, it's indeed somewhat faster to compute these together as opposed to separately.

# @stdlib/math/base/special/sincos
  rate: 5562353.802569125
  ... 
ok 3 benchmark finished
# @stdlib/math/base/special/sincos::separate-evaluation
  rate: 3983339.1105692126
  ... 
ok 6 benchmark finished
# @stdlib/math/base/special/sincos::in-place
  rate: 5768924.816521943
  ... 
ok 9 benchmark finished
# @stdlib/math/base/special/sincos::separate-in-place-evaluation
  rate: 4236238.051161047
  ... 
ok 12 benchmark finished
# @stdlib/math/base/special/sincos::built-in
  rate: 6348913.896061486
  ... 
ok 15 benchmark finished
# @stdlib/math/base/special/sincos::built-in-in-place
  rate: 5916167.422497482
  ... 

@stdlib-js/reviewers

@kgryte
Copy link
Member

kgryte commented Aug 23, 2017

Awesome!!!!

@rreusser
Copy link
Member Author

rreusser commented Aug 23, 2017

Was actually super easy! (You might be disappointed when you actually look at the implementation 😄) It's really just a straight up interleaving of the two since the majority of the computation is just duplicated.

@kgryte kgryte added Feature Issue or pull request for adding a new feature. Math Issue or pull request specific to math functionality. labels Aug 23, 2017
@rreusser
Copy link
Member Author

It did seem like perhaps the reduction would be worth factoring out into another module. As far as I can tell, it's just duplicated identically for the sin/cos/sincos modules.

@Planeshifter
Copy link
Member

Planeshifter commented Aug 23, 2017

Yes, indeed. One thing on my todo-list is to move rem_pio_2.js and the associated files to their own package (@stdlib/math/base/special/rempio2).

@rreusser
Copy link
Member Author

rreusser commented Aug 23, 2017

@Planeshifter yeah, it looks like some of the cos and sin modules have been linted differently but that they're in fact identical in function (as best as I can tell; it might be a problem if not!)


<section class="links">

[sine]: https://en.wikipedia.org/wiki/Sine
Copy link
Member

@kgryte kgryte Aug 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we point to the base special function here?

[@stdlib/math/base/special/sin]: https://github.com/stdlib-js/stdlib

Ditto for cosine. This allows us to direct people to our internal docs before sending them outbound.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we would have:

[@stdlib/math/base/special/sin]: https://github.com/stdlib-js/stdlib
[@stdlib/math/base/special/cos]: https://github.com/stdlib-js/stdlib

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that @Planeshifter !

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b.end();
});

bench( pkg+'::separate-in-place-evaluation', function benchmark( b ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate-evaluation,in-place. This is not yet documented, but we allow specifying multiple namespaces by delineating each namespace with a comma. This allows us to group here, e.g., the in-place benchmarks together and the separate-evaluation benchmarks, etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b.end();
});

bench( pkg+'::built-in-in-place', function benchmark( b ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

built-in,in-place

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x = ( 20.0 * rand_double() ) - 10.0;
y = sin( x );
z = cos( x );
if ( y != y ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check also that z is not NaN, otherwise the compiler may remove L87.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's the purpose of those checks. ✅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. When benchmarking, you need to do things to prevent the compiler from optimizing your code away. One means of doing so is to always provide changing input and validate output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
elapsed = tic() - t;
if ( y != y ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check z, as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```
"""
function benchmark()
t = BenchmarkTools.@benchmark sincos( (20.0*rand()) - 10.0 ) samples=1e6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try running this? I believe this will not work since sincos is defined outside the macro. Prefix sincos with a $.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both work. With $ is faster, but by an amount that doesn't seem statistically significant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. What version of Julia and Benchmarktools are you using?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

julia> VERSION
v"0.5.1"

julia> BenchmarkTools.VERSION
v"0.5.1"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am on 0.5.2 for both and previously encountered errors when not using $ for non-inlined functions. In any event, if it works, it works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha patch version lol.

* v = sincos( NaN );
* // returns [ NaN, NaN ]
*
* var out = new Float64Array( 2 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a separate example.

@example
var sincos = require( '@stdlib/math/base/special/sincos' );

...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// MAIN //

/**
* Return the last three digits of `N` with `y = x - N*pi/2` so that `|y| < pi/2`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Return/Returns/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -0,0 +1,379 @@
/* eslint-disable no-plusplus */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed.

Copy link
Member Author

@rreusser rreusser Aug 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to fail linting without this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/Users/rreusser/projects/node/stdlib/stdlib/lib/node_modules/@stdlib/math/base/special/sincos/lib/kernel_rem_pio2.js
   93:3   warning  Comments should not begin with a lowercase character                      capitalized-comments
  111:1   warning  Function 'compute' has too many statements (105). Maximum allowed is 100  max-statements
  128:22  error    Unary operator '++' used                                                  no-plusplus
  128:27  error    Unary operator '--' used                                                  no-plusplus
  135:31  warning  Comments should not begin with a lowercase character                      capitalized-comments
  198:5   warning  Comments should not begin with a lowercase character                      capitalized-comments
  311:3   warning  Comments should not begin with a lowercase character                      capitalized-comments
  356:23  error    Unary operator '++' used                                                  no-plusplus
  356:28  error    Unary operator '++' used                                                  no-plusplus
  373:3   warning  Comments should not begin with a lowercase character                      capitalized-comments

✖ 10 problems (4 errors, 6 warnings)
  0 errors, 5 warnings potentially fixable with the `--fix` option.

'use strict';

/*
* The following copyright, license, and long comment were part of the original implementation available as part of [FreeBSD]{@link https://svnweb.freebsd.org/base/release/9.3.0/lib/msun/src/k_sin.c?view=co}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function pulled from fdlibm? If not, should be able to remove this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a fairly trivial blend of the two versions (from sin and cos) that are. A few multiplications are shared between the two so that I simply put them in the same function call. I've modified the comment to reflect that more precisely:

/*
* The following copyright and license were part of the original implementation available as part of FreeBSD implementations of [k_sin.c]{@link https://svnweb.freebsd.org/base/release/9.3.0/lib/msun/src/k_sin.c?view=co} and [k_cos.c]{@link https://svnweb.freebsd.org/base/release/9.3.0/lib/msun/src/k_cos.c?view=co}..
*
* The implementation follows the original sine and cosine kernels, but has been modified for JavaScript and combined into a single function.
*/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! That works.

@kgryte
Copy link
Member

kgryte commented Aug 23, 2017

@Planeshifter May be good as well to move kernel_rempio2 to its own package, as well.

Copy link
Member

@Planeshifter Planeshifter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, Ricky!


Parameters
----------
[out]: array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use square brackets in the REPL help texts, but a (optional) after the type instead. This should be out: Array|TypedArray|Object (optional), since one can supply any collection for out.

*
* var out = new Float64Array( 2 );
*
* var v = sincos( out, 0.0 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-declaration of v. Should remove the var.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've separated this into two examples so that this should no longer be a re-declaration. ✅

/**
* Simultaneously computes the sine and cosine of a number.
*
* @param {array} [out] - destination array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object types should always be capitalized, e.g. @param {Array}, @param {Function}, and @param {Object}.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. Besides these standard JSDoc type annotations, you might want to have a look at our custom type definitions, which live here: https://github.com/stdlib-js/stdlib/tree/develop/tools/docs/jsdoc/typedefs

* Computes the sine and cosine on \\( \approx [-\pi/4, \pi/4] \\) (except on \\(-0\\)), where \\( \pi/4 \approx 0.7854 \\).
*
* @private
* @param {array} out - destination array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize array.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would I be correct to make this Array|TypedArray|Object?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct and wrapped in () because the value could be one of one or more different types/values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for catching this. In case of multiple accepted types in JSDoc, they should be enclosed in parentheses: (Array|TypedArray|Object)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, (Array|TypedArray|Object) or {Array|TypedArray|Object}?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both.

{(Array|TypedArray|Object)}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've attempted to fix this in the source files. They are now to the best of my knowledge correct.

* @param {number} x - input value (assumed to be bounded by `~pi/4` in magnitude)
* @param {number} y - tail of `x`
* @param {number} iy - indicates whether `y` is `0` (if `iy = 0`, `y` assumed to be `0`)
* @returns {array} sine and cosine (in radians)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize array.

@Planeshifter
Copy link
Member

@kgryte My current thinking was to just house kernel_rempio2 inside of rempio2. Would we have any other packages which would need to use kernel_rempio2 on their own?

@kgryte
Copy link
Member

kgryte commented Aug 23, 2017

In fdlibm, it is an independent file. http://www.netlib.org/fdlibm/

@kgryte
Copy link
Member

kgryte commented Aug 23, 2017

Also, I cannot remember kernel_rempio2 explicitly, but these kernel functions tend to crop up from time to time, e.g., in low level Julia/C implementations.

@stdlib-js stdlib-js deleted a comment from kgryte Aug 23, 2017
// Distill `q[]` into `IQ[]` in reverse order...
z = q[ jz ];
j = jz;
for ( i = 0; j > 0; i++, j-- ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disable the line rule on L129.

// eslint-disable-line no-plusplus

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter does not like complex for loop iteration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Set up `F[0]` to `F[jx+jk]` where `F[jx+jk] = IPIO2[jv+jk]`:
j = jv - jx;
m = jx + jk;
for ( i = 0; i <= m; i++, j++ ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disable the lint rule on this line.

// eslint-disable-line no-plusplus

Can then remove top-level disabling of lint rule.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* | 3 | -C | S | -1/T |
*
*
* @param {(Array|TypedArray|Object)} out - destination array
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this now @private since it's only indirectly exposed through the variadic wrapper in main.js?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var bench = require( '@stdlib/bench' );
var randu = require( '@stdlib/math/base/random/randu' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var pkg = require( './../package.json' ).name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This require statement should be moved to L10.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ sin( x ), cos( x ) ];
end


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra newline.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameters
----------
out: Array|TypedArray|Object (optional)
Destination array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing period.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Returns
-------
y: array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be capitalized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather, should be Array|TypedArray|Object

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B
];

// Double precision array, obtained by cutting `pi/2` into `24` bits chunks...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 bit chunks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* Simultaneously computes the sine and cosine of a number.
*
* #### Method
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/####/##/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dir = dirname( file );

# Negative medium sized values:
x = linspace( -256.0*pi, 0.0, 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

gen( x, out );

# Positive medium sized values:
x = linspace( 0.0, 256.0*pi, 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the Julia linter (https://lintjl.readthedocs.io/en/stable/messages/) is not as configurable as ESLint and, in fact, not configurable at all. So we are left manually enforcing these kinds of things.

gen( x, out );

# Negative large values:
x = linspace( -2.0^20*(pi/2.0), -2.0^60*(pi/2.0), 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

gen( x, out );

# Positive large values:
x = linspace( 2.0^20*(pi/2.0), 2.0^60*(pi/2.0), 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

gen( x, out );

# Negative huge values:
x = linspace( -2.0^60*(pi/2.0), -2.0^1000*(pi/2.0), 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

gen( x, out );

# Positive huge values:
x = linspace( 2.0^60*(pi/2.0), 2.0^1000*(pi/2.0), 4000 )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon.

tol = EPS * abs( sine[i] );
t.ok( delta <= tol, 'within tolerance. x: '+x[i]+'. Value: '+y[0]+'. Expected: '+sine[i]+'. tol: '+tol+'. delta: '+delta+'.' );
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty line to be consistent with other test blocks.

tol = EPS * abs( sine[i] );
t.ok( delta <= tol, 'within tolerance. x: '+x[i]+'. Value: '+y[0]+'. Expected: '+sine[i]+'. tol: '+tol+'. delta: '+delta+'.' );
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty line to be consistent with other test blocks.

@rreusser
Copy link
Member Author

rreusser commented Aug 24, 2017

@kgryte I've fixed the most recent issues. Some of these issues are minor style issues in the modules from which I copied the bulk of this module. What's the best way to address those? With a separate PR? By pointing them out to you so that you can fix them? No preference on my side. Just let me know.

@kgryte
Copy link
Member

kgryte commented Aug 24, 2017

If you want to submit a separate PR with all those knits, that is fine with me!

@kgryte
Copy link
Member

kgryte commented Aug 24, 2017

Will wait on Circle CI to finish one more round of testing before squashing and merging. Thanks for all your work on this, @rreusser!

@kgryte kgryte merged commit 37269d3 into stdlib-js:develop Aug 24, 2017
@rreusser rreusser deleted the sincos branch August 24, 2017 04:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Issue or pull request for adding a new feature. Math Issue or pull request specific to math functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants