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

Behaviour of non-finite complex numbers. #5

Open
harrysarson opened this issue Apr 21, 2017 · 26 comments
Open

Behaviour of non-finite complex numbers. #5

harrysarson opened this issue Apr 21, 2017 · 26 comments

Comments

@harrysarson
Copy link
Contributor

I was looking into issue josdejong/mathjs#804 regarding the behaviour of negative numbers raised to infinite powers.

Support for non-finite complex numbers seems to be incomplete in complex.js and I was wondering if it was something that you are planning to support more comprehensively. For example:

  • new Complex(-0.5).pow(Infinity) currently gives NaN which is inconsistant with the behaviour of Math.pow(-0.5, Infinity) which (correctly) gives 0.

Additionally I found a couple of bug which need fixing regardless of whether the library is extended to add proper support for complex numbers.

  • new Complex(0).inverse() currently gives 0.
  • new Complex(Infinity).equals(Infinity) currently returns false.
@infusion
Copy link
Owner

Oh, that are good findings! Thanks a lot for the ticket and notifying me about #804! I'll look into this. About the last point, it's a mere philosophical question if infinity should equal infinity, or more specifically is 1/0 == 2/0? JavaScript simply compares the infintiy bit, but other languages treat this case differently.

@harrysarson
Copy link
Contributor Author

Yeah that's fair re the equality - an isFinite might be a better way to test for infinite numbers although !isFinite(z) && z.im === 0 && z.re > 0 is far more unwieldy than z.equal(Infinity).

Is full support for infinite values (eg in the pow function) worth increasing the size of the library?

@infusion
Copy link
Owner

Okay, I added a new branch "infinity": f06449f

I added a new symbol Complex.Infinity, which sets real and imaginary part to infinity. This symbol is returned for inv(0), div(z, 0) and if the angle for cos/sin in pow is becoming infinity (which originally forced the number to become nan).

The actual code changes don't change the size of the library dramatically, so I would say it's worthwhile to add the feature.

I would say we keep equal as it is, but think about adding isFinite (which is true <=> isFinite(re)&isFinite(im)).

@harrysarson
Copy link
Contributor Author

harrysarson commented Apr 23, 2017

isFinite sounds like a good solution.

I noticed that currently complex(0).div(0) === complex(0). This is in both the master and infinity branches.

Additionally currently complex(1).div(0) === complex(Infinity, 0) where as complex(0).inverse() === complex(Infinity, Infinity). I am not sure about the best behaviour for this as I feel that the division of two real numbers should be real but also that inverse(z) should be equivalent to 1/z.

Finally complex(0, Infinity).mul(1) === complex(NaN, Infinity) at the moment.

@infusion
Copy link
Owner

Hm, yep I thought the same way and I think a real infinity should stay real. However, wolframalpha decided (maybe for the same reason) to print complex infinity for any case, the complex and real: http://www.wolframalpha.com/input/?i=1%2F0

0/0 Should be fixed, right. NaN is an approproate value for that.

The different behavior of div and inverse must be fixed as well.

About the last one, the multiplication, it happens to become NaN since 0Infinity=NaN. I see your point that the multiplicative neutral element should return the same value after multiplication (which would be the case, if we treat 0Infinity as 0. I must have a look, if this can be generalized this way.)

@infusion
Copy link
Owner

infusion commented Apr 24, 2017

Okay, there is an update in infinity branch.

  • isFinite was added
  • 0/0 is now complex infinity as well, even if it's not defined. Let's see if we can get along with complex infinity and not returning a "complex nan" at all - since I don't want to add a special check for this rare case.
  • About imaginary infinity times one - I'm not sure if we should fix that (see last post).
  • 1/0=complex inf is consistent with inv(0)=complex inf

@harrysarson
Copy link
Contributor Author

  • I am not a big fan of 0/0 === ComplexInfinity as that would be different from every other maths language and I don't think consistent with complex maths although I had a look at the wikipedia article and it all went over my head.

  • I had a go at implementing full support for infinity in the mul function see my pr. I don't like how messy it is but I couldn't find anyway to handle non finite numbers apart from bashing every case.

  • I also had look at the parse function and added support for infinite complex numbers in the {r, phi} form. I don't think Infinity + 5i makes any sense so unless the number is purely real or purely imaginary the parse function sets both the re and imaginary components to Infinity. This slightly reduces the number of cases I had to include in the mul function.

@harrysarson
Copy link
Contributor Author

harrysarson commented Jul 12, 2017

Hi

What is the purpose of these lines in the divide function?

return new Complex(
  (a !== 0) ? (a / 0) : Infinity, 
  (b !== 0) ? (b / 0) : Infinity);

If a is not zero then the expression evaluates to Infinity anyway.

https://github.com/infusion/Complex.js/blob/infinity/complex.js#L387-l388.

I believe that the whole of the if (0 === c) conditional could be removed.

@infusion
Copy link
Owner

The purpose is to keep the sign of each Infinity.

@harrysarson
Copy link
Contributor Author

harrysarson commented Jul 13, 2017

But as both the real and imaginary parts will be infinite the return value will get converted to complex infinity anyway.

I am also reasonably convinced that new Complex(0).div(0) should be ComplexNaN; I think its worth a special case.

@balagge
Copy link

balagge commented Aug 21, 2017

Hi!
I would like to ask how this issue is progressing. I am interested in having complex infinity fixed in the library. After all this is a Complex library so I would assume it treats infinities mathematically correctly.
Most of the workings of complex infinity is on the wikipedia page https://en.wikipedia.org/wiki/Riemann_sphere
But I am also happy to summarize this here, if needed.

@harrysarson
Copy link
Contributor Author

@balagge have a look at pull #12, I have had a go at making the required fixes but I don't have an intensive knowledge of the maths so if you could have a look through the changes that would be grand.

Otherwise we are waiting on @infusion

@balagge
Copy link

balagge commented Oct 11, 2017

Unfortunately I currently (and for quite a while) do not have the time to understand the code and test it. What I can do is write down what I consider the mathematically correct behavior.

Below c, c1, c2, etc. denotes any complex infinity object, z, z1, z2, etc. denotes any Complex object other than a complex infinity (i.e. finite)

Construction and equality

  1. complex infinities should be constructed by the Complex constructor if either the real part or the imaginary part is infinite (Infinity and -Infinity does not matter), or the radius is positive Infinity. So the following are all complex infinities:
    c = new Complex(Infinity, 1)
    c = new Complex(0,Infinity)
    c = new Complex({re:Infinity, im:-Infinity})
    c = new Complex({arg:3, abs:Infinity} (however, abs:-Infinity is invalid. also, arg:Infinity and arg:-Infinity should be invalid)
  2. Any c should be a Complex object different from any z, so
    z.equals(c) === false and
    c.equals(z) === false should always hold (note that c is any complex infinity object and z is any finite complex object).
  3. Complex Infinities should be equal (even if two different object)., so
    c1.equals(c2) === true should hold for any (different) complex infinity objects.

Addition, subtraction, negation

  1. Adding infinity to any finite complex should yield a complex infinity, so
    z.add(c1).equals(c2) === true , and
    c1.add(z).equals(c2) === true should always hold (note the usual usage of letters)
  2. Adding complex infinities is not defined (UNLIKE real infinities), so
    c1.add(c2) should not have a value (NaN or something else? I do not know how exactly to represent undefined operations, but probably there is some way to do that already)
  3. Negation returns the same complex infinity (UNLIKE real infinities), so
    c1.neg().equals(c2) === true should always hold
  4. Subtraction is the same as adding the negated operand. Specifically
    z.sub(c1).equals(c2) === true,
    c1.sub(z).equals(c2) === true,
    c1.sub(c2) is invalid (not defined)
  5. sign is not defined, so
    c.sign() should not have a value.

Real and imaginary part, argument and absolute value

  1. real part is not defined. This may be a problem, but c.re should NOT have a definite value
  2. imaginary part is not defined.
  3. argument is not defined, so c.arg() should not have a value
  4. absolute value is positive infinity, so c.abs() === Infinity should hold.

Multiplication

  1. Anything (finite or infinite), except zero, multiplied by a complex infinity is a complex infinity, so
    z.mul(c1).equals(c2) === true, and c1.mul(z).equals(c2) === true unless z is zero.
    z.mul(c1) and c1.mul(z) is not defined if z is zero.
    c1.mul(c2).equals(c3) === true

Division

Division has multiple cases. both the dividend and the divisor must be examined and three cases separated: zero, non-zero (but finite), infinity. This yields 9 possible combinations (here I use more traditional notation and z', z1', z2', etc. mean nonzero, finite complex numbers)
13) 0 / 0 is not defined
14) 0 / z' is 0, as usual.
15) 0 / ∞ is also 0.
16) z' / 0 is .
17) z1' / z2' is normal division of complexes.
18) z1' / ∞ is 0.
19) ∞ / 0 is not defined
20) ∞ / z' is
21) ∞ / ∞ is not defined.

Multiplicative inverse

  1. this follows from the division above, here I use 1/z notation instead of z.inverse(),
    1 / 0 is
    1 / z' is normal inverse of a complex
    1 / ∞ is 0.

This is the basic stuff, other functions require some more thinking but I will come back when I have some time

@harrysarson
Copy link
Contributor Author

Ah thanks, I'll have a look through these :)

@harrysarson
Copy link
Contributor Author

Yeah, I back everything except the following which I have questions about.

0. How about new Complex(Infinity, 0), should this not be real infinity?
If it is then by symmetry new Complex(0, Infinity) should be imaginary infinity.

2. See #5 (comment), I would be happy for ComplexInfinity.equals(ComplexInfinity) === true.

4. Yeah we have ComplexNaN for times like this.

8. Hmm, need to think more about this. Possibly worthy of a separate issue.

22. Also requires thought.
I am of the opinion that new Complex(1, 0).div(0) should equal 1 / 0. When using real numbers the behaviour of this library should be identical to using JavaScript numbers.
I realise though that mathematically this is wrong but I think I can live with that.

@balagge
Copy link

balagge commented Oct 11, 2017

I see what you mean. Unfortunately, it is impossible to extend the complex plane with a single complex infinity in such a way that it is mathematically reasonable ("correct") and operations for real numbers behave exactly as on the (extended) real number line at the same time.

The closest thing is using "directed infinities". The real infinity (javascript: Infinity) is then "an infinity in the direction of 1". (direction = when viewed from point 0, or simply "to the east"). The real negative infinity (javascript: -Infinity) is "an infinity in the direction of -1, or simply "to the west". You can have then an infinite number of infinities in all possible directions (like infinity in the direction of i, which is infinity "to the north", but also "infinity in the direction of 1+i", or northeast.

This is possible, and indeed, Wolfram uses this approach. It has a ComplexInfinity and an infinite number of DirectedInfinity[z] different infinities (where z is any non-zero complex number). The "well-known" Infinity in javascript is then not ComplexInfinity, but DirectedInfinity[1] in Wolfram. (the square brackets are used around function arguments). Or, with traditional notation, where ∞ is the real positive infinity (javascript Infinity) :
∞ = 1∞ = x∞ (where x is any positive real number),
-∞ = (-1)∞ = x∞ (where x is any negative real number)

For complex infinity, wolfram uses a symbol similar to this: ⧝ (U+29DD). But then
∞ ≠ ⧝ unfortunately, and
1 / 0 = ⧝ and 1 / 0 ≠ ∞ (on the complex plane), however, 1 / 0 = ∞ (on the real number line). :(

This becomes even more problematic, because you then have to define all operations for all of these new infinities, and some of the operations are again undefined. To my knowledge there is little, if any, practical use for these directed infinities. But I may be mistaken.

https://en.wikipedia.org/wiki/Directed_infinity

http://mathworld.wolfram.com/DirectedInfinity.html

https://www.wolframalpha.com/input/?i=directed+infinity

@balagge
Copy link

balagge commented Oct 11, 2017

btw javascript has some mathematically incorrect behavior as well, so maybe it is not a great idea to follow it.

example: Math.pow(-2,Infinity), which yields Infinity. This is incorrect. On the real number line, this has no value, and therefore should probably be NaN (because the sequence (-2)^n does not converge). On the complex plane, if we use Infinity for both the real ∞ and the complex infinity ⧝, it would be true, in the sense that (-2)^∞ =⧝. However, Infinity cannot mean both the positive real infinity ∞ and the complex infinity ⧝. Those behave differently, for example an easy way to tell the difference between the two is this: ⧝+⧝ is not defined, but ∞+∞ = ∞. Since in javascript Infinity + Infinity yields Infinity we can be sure that Infinity represents the real infinity, not the complex one.

@balagge
Copy link

balagge commented Oct 12, 2017

This does not mean of course that the javascript behavior for 1 / 0 yielding Infinity is incorrect. It is correct on the real number line. But since Complex.js models the complex plane, not the real numbers, it should follow complex math and the mathematically correct behavior for new Complex(1, 0).div(0) should be ComplexNaN then (if we do not have complex infinity), or, better still, ComplexInfinity.

@harrysarson
Copy link
Contributor Author

This is MATLAB's behaviour, my preference would be for Complex.js to be the same.
received_1840415552655119

@balagge
Copy link

balagge commented Oct 12, 2017

I see. Looks kind of silly, based on these it seems MATLAB models 8 selected directions on the complex plane (i∞, (1+i)∞, 1∞, (1-i)∞, (-i)∞, (-1-i)∞, (-1)∞, (-1+i)∞ ) which is very arbitrary. I do not know MATLAB, but I remember vaguely that I came across some other case where it had questionable behavior.

The real question is: what is the practical use of all these infinities? I see the following use cases generally:

  • (real) infinity is sometimes useful as an upper or lower bound instead of an actual number value. So if you have a constraint like a < x < b , then you can easily use Infinity and -Infinity instead of a number for a or b and that pretty much simplifies your code, especially if the value of a and b comes from some configuration option or from another computation. But this case applies to reals only.
  • infinity may result as a "final" value from some computation you execute, which otherwise cannot yield a number (or Complex). Instead of treating these cases as NaN or even raising some exception, this can be sometimes useful to detect some edge case. NaN is a pretty useless construct because you have no clue what causes it. Some form of infinity, however, may have some semantics in the particular scenario that can be handled meaningfully. This applies to both real and complex computations as well.
  • same as above, but the resulting infinity is not a "final" value, but some intermediate value. It then may be meaningfully processed at a later stage of the computation. This can be very handy, like if you have a formula: 1 / (1 + f(x)), and maybe your f(x) can return Infinity, the computation may yield a perfectly valid result (0 in this case, assuming real domain). Again, this can simplify your code dramatically.

These are the cases I have ever came across. The first one is the most common (at least for me), but does not apply to complexes (no ordering on complex plane).

The second one can be achieved by any infinity construct (whether a single complex infinity, or several directed infinities, or the MATLAB version with 8 selected directed infinities - I still think this is silly). The more different kinds of infinities you have, the less useful this becomes because it will be difficult and error prone how you check the return value for these infinities.

The third one is a piece of cake, provided that the underlying logic is mathematically sound. It does not occur frequently, but when it does, it can greatly simplify code (no need to separate a lot of different edge cases). However, if some arbitrary rules are used, like in MATLAB, then most probably the result will not be what you expect in a particular scenario, so you end up checking those edge cases despite the built in infinity constructs / computation rules, hence no improvement / simplification in your code and the infinities become useless like NaN.

Of course this is by no means a detailed analysis, rather just my personal opinion based on (my particular) experience and intuition. Which still tells me that the best / most elegant version would be a single complex infinity. It would be great, however, to hear opinions from others as well.

@balagge
Copy link

balagge commented Oct 12, 2017

Nevertheless I have to give credit to the argument that it is a nice feature to have that things like 1/0 can be computed without an underlying assumption of the domain (reals or complexes). In other words, 1/0 yields the "same" result for numbers and Complexes. On the other hand, this library is about complex numbers, not a general purpose math engine, so such an assumption is not a problem in this case.

@harrysarson
Copy link
Contributor Author

Sorry about the delay. :)

Having these 8 infinities is easy to implement with each complex object containing two fields (re and im) where as anymore infinities would require much more involvement. I think that is why MATLAB does it this way.

The more different kinds of infinities you have, the less useful this becomes because it will be difficult and error prone how you check the return value for these infinities.

I propose adding an isFinite() function which would be the recommended way to test for infinite complex numbers. Especially as the current behaviour is for equals() to consider all infinite values unequal.

Therefore options:

  1. A single complex infinity.
  2. ± real infinity, ± imaginary infinity, complex infinity
  3. ± real infinity, ± imaginary infinity, 4 complex infinities one in each quadrant.

I vote for 2. @infusion Do you have an opinion?

@harrysarson
Copy link
Contributor Author

see #14 as well.

@derknorton
Copy link
Contributor

I vote for option 1 (Riemann Sphere) that @balagge recommended. It is both mathematically and programmatically elegant and shouldn't add much if any to the size of the library. I have been looking for a complex library that implemented it for a while now...

@harrysarson
Copy link
Contributor Author

harrysarson commented Feb 1, 2018

Issue resolved by #17, we can close this issue as soon as a new version is pushed to npm.

I think these changes are semver major just incase anyone was previously doing new Complex(0).div(1) which used to give { re: Infinity, im: 0 } and in the new version gives { re: Infinity, im: Infinity }

@harrysarson
Copy link
Contributor Author

@infusion could you release a version to npm?

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

No branches or pull requests

4 participants