JuliaMath / Infinities.jl

A Julia package for representing infinity in all its forms

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Project plan

dlfivefifty opened this issue · comments

  1. Infinity <: Real (replaces Infinity.Infinite)
  2. InfiniteCardinal{K} <: Integer (InfiniteCardinal{0} replaces InfiniteArrays.Infinity and InfiniteCardinal{K} replaces ContinuumArrays.AlephInfinity{K})
  3. RealInfinity <: Real
  4. ComplexInfinity <: Number for angled infinity
  5. InfiniteTime <: TimeType
  6. InfExtended*

I'll plan to do 1--4 by copying over the code from InfiniteArrays.jl.

@cjdoris For consistency in naming we should choose one of the following:

  1. RealInfinity and ComplexInfinity
  2. SignedInfinity and AngledInfinity

In the previous proposal it mixes terms. Which of the two do you prefer?

I prefer ComplexInfinity over AngledInfinity because then it's clear it really is meant to be a complex number, and also there are many kinds of angles.

But then if we go with 1 then logically we should also have PositiveRealInfinity (or UnsignedRealInfinity or URealInfinity) instead of Infinity.

I think just Infinity is fine for PositiveRealInfinity, as it will be the one with as the shorthand. The README can make this clear.

I'm OK with that.

Hmm, Note that ComplexInfinity{Bool} is actual the same as RealInfinity... Does this change anything design wise?

Does it? What does the type parameter mean?

I was thinking about it earlier, and I definitely think that ComplexInfinity{T} should represent the number z * ∞ by the non-zero complex number z :: Complex{T} and not by the angle. Firstly, this is more consistent with the existing Complex type, which uses Cartesian coordinates. Secondly, this doesn't get in the way of using Gaussian integers Complex{Int} or infinite Gaussian integers ComplexInfinity{Int} and InfExtendedComplex{Int}.

Does it? What does the type parameter mean?

See

struct ComplexInfinity{T<:Real} <: Number

I currently represent it by the angle, but stored as signbit, that is π * a.signbit. The reason I did this is so that ±∞ are exactly representable. But of course they are also exactly representable if we store z.

But I don't think I actually use InfiniteArrays.OrientedInfinity for anything important, so we can change it to your suggestion.

Projective Infinity (the infinity of the Riemann sphere)

Yes please. As a general rule I think x*∞ should be represented exactly if possible, whatever the type of x. On the whole this might mean storing x exactly, but for reals a sign bit suffices.

Projective Infinity (the infinity of the Riemann sphere)

There's already https://github.com/scheinerman/RiemannComplexNumbers.jl

No reason we couldn't add projective infinities in general though, if there's demand.

Hmm, I think what we really want are union types that are subtypes of Real or Number. In other words, we wish we could do:

const RiemannSphere{T} = Union{Complex{T}, ComplexInfinity{T}}

but we cannot since Union is not <: Number.

But what do you think about this:

struct IntegerUnion{TYPES<:Union} <: Integer
    types::TYPES
end
struct RealUnion{TYPES<:Union} <: Real
    types::TYPES
end
struct ComplexUnion{TYPES<:Union} <: Number
     types::TYPES
end

const ExtendedInt = IntegerUnion{Union{Int,RealInfinity}}
const ExtendedComplex{T} = ComplexUnion{Union{Complex{T},ComplexInfinity{T}}}
const RiemannSphere{T} = ComplexUnion{Union{Complex{T},Infinity}}

Sorry, I was wrong:

julia> Union{Real,Complex} <: Number
true

So actually, my new proposal is have no special types for extended numbers and just do:

const ExtendedInt = Union{Int,RealInfinity}
const ExtendedComplex{T} = Union{Complex{T},ComplexInfinity{T}}
const RiemannSphere{T} = Union{Complex{T},Infinity}

What if any draw back would there be?

Type instability and promotion craziness! This is what InfExtendedReal{Int} solves, by being a single concrete type.

Small union types do not suffer from type instability: this is the whole point of Union{T,Missing}.

I have done something similar.

  • Performant logic requires this sort of an exhaustive approach (afaik).
  • could be streamlined some with macros and @eval loops
using Base.Checked

abstract type IntegerInfinity end
struct PosIntInfinity <: IntegerInfinity end
struct NegIntInfinity <: IntegerInfinity end

const IntInfinity = Union{PosIntInfinity, NegIntInfinity}
const ExtendedInt = Union{Int, IntInfinity}

const PosIntInf = PosIntInfinity()
const NegIntInf = NegIntInfinity()

const InfInt = IntInfinity()
const ExtendedInt = Union{Int, IntInfinity}

function Base.(*)(x::Int, y::Int)::ExtendedInt
    z, ovf = mul_with_overflow(x, y)
    return ovf ? (signbit(x) === signbit(y) ? PosInfInt : NegInfInt) : z
end
function Base.(*)(x::Int, y::PosIntInfinity)::IntInfinity
    signbit(x) ? NegIntInf : PosIntInf
end
function Base.(*)(x::Int, y::NegIntInfinity)::IntInfinity
    signbit(x) ? PosIntInf : NegIntInf
end
function Base.(*)(x::PosIntInfinity y::Int)::IntInfinity
    signbit(y) ? NegIntInf : PosIntInf
end
function Base.(*)(x::NegIntInfinity, y::Int)::IntInfinity
    signbit(y) ? PosIntInf : NegIntInf
end
Base.(")(x::PosIntInfinity, y::PosIntInfinty)::PosIntInfinity = PosIntInf
Base.(")(x::PosIntInfinity, y::NegIntInfinty)::NegIntInfinty  = NegIntInf
Base.(")(x::NegIntInfinity, y::PosIntInfinty)::NegIntInfinty  = NegIntInf
Base.(")(x::NegIntInfinity, y::NegIntInfinty)::PosIntInfinty  = PosIntInf

Perhaps we should nail down Infinity first, say as v0.1, and we can think about extended reals for v0.2? That way we can do some testing if a union type suffices.

Small union types do not suffer from type instability: this is the whole point of Union{T,Missing}.

That relies on the specifics of the Julia compiler, and the notion of "small union". If I have a x :: Vector{Union{Int,RealInfinity}} then map(Vector, x) is a Vector{Union{Vector{Int}, Vector{RealInfinity}}}. Is that still type-stable and fast? Is it what the user really wanted? The current API for numbers is that binary operations are type-stable and promote to a common type.

Your example doesn't make any sense since Vector(5) and Vector(∞) are not defined.

Your logic applies also to Missing... if its good enough for Missing isn't it good enough for us? In other words, to make a convincing argument you would have to point to a specific weakness in Union{T,Missing} that is important enough to justify the extra type.

To hammer down the point, a working example does type inferrence with union types:

julia> @inferred(map(something, Union{Int,Nothing}[1,2]))
2-element Array{Int64,1}:
 1
 2

Or an example more close to home:

julia> closeint(::Infinity) = typemax(Int)
closeint (generic function with 1 method)

julia> closeint(x::Int) = x
closeint (generic function with 2 methods)

julia> @inferred(map(closeint, [1,5,∞]))
3-element Array{Int64,1}:
                   1
                   5
 9223372036854775807

That relies on the specifics of the Julia compiler

No it does not, as the behaviour for Missing and Nothing is documented and advocated as a good design

julia> map(a->[a], Union{Int,Infinity}[1,Infinity()])
2-element Array{Array{T,1} where T,1}:
 [1]
 [Infinity()]

julia> @inferred ans[1]
ERROR: return type Array{Int64,1} does not match inferred return type Array{T,1} where T
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] top-level scope at REPL[13]:1

The above is exactly the sort of thing that someone doing numerical computing shouldn't have to think about: the existing API for number types is that they promote to a common concrete type when mixed together.

Also [1, Infinity()] is a Vector{Real} and 1:Infinity() doesn't work at all.

I suppose one fix would be to have a promotion rule T<:Real + Infinity = Union{T,Infinity}, but that wouldn't fix the earlier issue.

Hmm, with the right overrides we can get things to work:

julia> Base.typejoin(::Type{Vector{Infinity}}, ::Type{Vector{Int}}) = Vector{Union{Int,Infinity}}

julia> Base.typejoin(::Type{Vector{Int}}, ::Type{Vector{Infinity}}) = Vector{Union{Int,Infinity}}

julia> Base.promote_rule(::Type{Int}, ::Type{Infinity}) = Union{Int,Infinity}

julia> map(a->[a], [1,Infinity()])
2-element Array{Array{Union{Infinity, Int64},1},1}:
 [1]
 [∞]

Though you are right it might be necessary to just have custom types, e.g., the last line is not type-inferred.

PS:

julia> map(a -> [a], [1,missing])
2-element Array{Array{T,1} where T,1}:
 [1]
 [missing]

So it seems like a missing case in the compiler.

Hmm, with the right overrides we can get things to work:

My point was not specific to Vector, but applies to any type that has a numeric type parameter. Also, you really shouldn't overload typejoin, it has a very precise meaning that your definitions break.

Arithmetic of ComplexInfiniity

I get File Not Found. <<<< see below for a pdf version >>>>

But the name reminds me to say that RiemannSphere{T} = Union{Complex{T}, Infinity} doesn't make conceptual sense because Infinity() is the positive real infinity, which is not the same thing as projective infinity. So if anything it should be RiemannSphere{T} = Union{Complex{T}, ProjectiveInfinity}.

Also, you really shouldn't overload typejoin

Agreed, you won the argument and these should be special types (at least until Julia v2.0 comes along and improves Missing support even more)

Glad I won you around :)

What's happening in v2.0? Where do I go to find out such things?

What's happening in v2.0? Where do I go to find out such things?

Was just being facetious...but perhaps they can be convinced to improve the type inference. (It might be pretty easy actually with an improved Generator.)

Arithmetic of ComplexInfinity
(Wolfram web page as pdf, zoom in to read)