Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complex bundling, ComplexInfinity and floor #2114

Closed
PFPF opened this issue Feb 25, 2021 · 7 comments
Closed

Complex bundling, ComplexInfinity and floor #2114

PFPF opened this issue Feb 25, 2021 · 7 comments
Labels

Comments

@PFPF
Copy link

PFPF commented Feb 25, 2021

Hi! While using the library, I had a few questions/comments:
(1) Is there a way to only load the parts about numbers and complex numbers? Basically I only need functions from ℂ to ℂ (complex abs, sqrt, log etc, maybe also logicals), never need things like matrices and units. I'm not aiming to reduce the bundle size, but mainly for performance, and I hope to get rid of the unnecessary branchings (and also prevent some risks like #2088). Do you have suggestions about what I can do?
(2) It threw me off today that math.evaluate("i/0").toString() and math.evaluate("-(i/0)").toString() are both "Infinity"---the same "Infinity" as the actually-negatable Infinity. Can we call it something else like ComplexInfinity?
(3) Just a sidenote (I already fixed it in my code :)): I think the performance of math.floor has some potential to improve:

let c = -1e-200;
console.time('mathjs'); for(let i=0;i<10000;i++) math.floor(c); console.timeEnd('mathjs'); // around 500ms
console.time('builtin'); for(let i=0;i<10000;i++) Math.floor(c); console.timeEnd('builtin'); // around 1ms

Looking at the code, I think the main bottleneck---when the input is a number---is the two(!) calls of math.round (which is understandably pretty slow). I'll suggest the following workaround: when x has type number, we know (hopefully) that math.round(x) is equal to either Math.floor(x) or Math.ceil(x), so just try both of them! In the worst case, we call Math.floor and Math.ceil once and nearlyEqual twice, which still seems to be much faster than calling math.round.
Also, to me it makes more sense if this nearlyEqual business is off by default in math.floor. Currently it's off for complex numbers anyway (math.floor(math.Complex(-1e-16, -1e-16)) == Complex(-1, -1), as compared with math.floor(-1e-16) == 0). I'm leaning towards letting floor be consistent with js, and call the one with nearlyEqual math.toleratingFloor or something similar. That's just my personal preference though.

Pardon me for the wall of text! Many thanks for this awesome library.

@harrysarson
Copy link
Collaborator

(2) It threw me off today that math.evaluate("i/0").toString() and math.evaluate("-(i/0)").toString() are both "Infinity"---the same "Infinity" as the actually-negatable Infinity. Can we call it something else like ComplexInfinity?

We are working on this one in #804 and #2097!

@PFPF
Copy link
Author

PFPF commented Feb 25, 2021

Glad to know! After looking at #2097 I have a crazy idea: use (r,phi) instead of (re,im) to internally represent complex numbers. This way we can realize all these good stuff by having Complex({r: Infinity, phi: something finite}). Then "Inf i" will just be {r: Infinity, phi: pi/2} and "-Inf i" being {r: Infinity, phi: -pi/2}, but we can now represent much more things, like sqrt(sqrt(-Infinity)), and even be able to multiply i on "Inf i" to get back -Infinity (or its complex version). The generic ComplexInfinity can be represented by {r: Infinity, phi: NaN}, which can smoothly serve as the reciprocal of the complex zero, {r: 0, phi: NaN}.

I wrote a corresponding pow function here. Is that a world we like?

@josdejong
Copy link
Owner

(1) there is no ready-made solution for that but all the building blocks are there. It will require some work though. It boils down to:
- Create your own implementation of functions like add, subtract, multiply, etc. and pass those implementation to the factory functions of all other functions that you want to use. Some pointers:

https://mathjs.org/docs/custom_bundling.html
https://mathjs.org/examples/advanced/custom_evaluate_using_factories.js.html

(2) see comment of Harry. Feel free to weigh in in the discussion there!

(3) the mathjs version of floor does do type and signature checking on the inputs etc, that makes it slower. A built-in function like Math.floor is so fast that any overhead will be relatively large. If you already know what you're going to pass to the function, you could invoke the right signature directly: all signatures are exposed in the object math.floor.signatures, in this case you want to execute math.floor.signatures.number. And the mathjs implementation of floor handles round-off errors nicely using the nearlyEqual you're mentioning. It would be nice to implement this for complex numbers too. And good idea to make this configurable. It would be good to create a separate issue for all your ideas, else the'll get lost in this conversation.

(4) about storing complex numbers as (r, phi): that may be interesting, though I'm not sure if it will really improve things in the end: will most calculations become easier or faster? Do most calculations get more round-off errors? Or less?

@PFPF
Copy link
Author

PFPF commented Mar 7, 2021

Thanks for the detailed answers. I will try to understand the custom bundling more when I can, but I already found math.xxx.signatures really helpful: now by calling math.floor.signatures.Complex instead of math.floor on non-numeric inputs my code becomes ~30% faster and I'm pretty satisfied. Thanks for mentioning that property.

About (4): compared to the rectangular form, the polar form is (much) simpler and faster and more precise with abs, sign, sqrt, pow, multiplication and division, while it's (much) slower and less precise with [noncollinear] addition and subtraction. My opinion is that regarding infinities and zeros, the polar form makes way more sense. I've written a more detailed plan/argument here, and I'd be happy if this can be considered! (also @harrysarson)

@josdejong
Copy link
Owner

About (4): compared to the rectangular form, the polar form is (much) simpler and faster and more precise with abs, sign, sqrt, pow, multiplication and division, while it's (much) slower and less precise with [noncollinear] addition and subtraction. My opinion is that regarding infinities and zeros, the polar form makes way more sense. I've written a more detailed plan/argument here, and I'd be happy if this can be considered! (also @harrysarson)

That sounds interesting! It would be a huge breaking change of course, so it may to be easy to pull this off. I suppose besides understanding the upsides, we also need to have a clear overview of the downsides. It may be handiest to open a discussion at https://github.com/infusion/Complex.js/ (the library that is powering complex number calculations in mathjs).

@PFPF
Copy link
Author

PFPF commented Mar 9, 2021

Thanks! I opened an issue there.

@josdejong
Copy link
Owner

Closing this issue now, the concerns are addressed in separate issues if I'm not mistaken.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants