Infinities.jl icon indicating copy to clipboard operation
Infinities.jl copied to clipboard

Project plan

Open dlfivefifty opened this issue 5 years ago • 33 comments

  1. [x] Infinity <: Real (replaces Infinity.Infinite)
  2. [x] InfiniteCardinal{K} <: Integer (InfiniteCardinal{0} replaces InfiniteArrays.Infinity and InfiniteCardinal{K} replaces ContinuumArrays.AlephInfinity{K})
  3. [x] RealInfinity <: Real
  4. [x] 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.

dlfivefifty avatar Aug 12 '20 09:08 dlfivefifty

@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?

dlfivefifty avatar Aug 12 '20 09:08 dlfivefifty

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.

cjdoris avatar Aug 12 '20 09:08 cjdoris

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

dlfivefifty avatar Aug 12 '20 09:08 dlfivefifty

I'm OK with that.

cjdoris avatar Aug 12 '20 09:08 cjdoris

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

dlfivefifty avatar Aug 12 '20 09:08 dlfivefifty

Does it? What does the type parameter mean?

cjdoris avatar Aug 12 '20 09:08 cjdoris

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

cjdoris avatar Aug 12 '20 09:08 cjdoris

Does it? What does the type parameter mean?

See

https://github.com/JuliaMath/Infinities.jl/blob/3d27318c5d26b96aecfd8624d11aaba9749b43b3/src/Infinities.jl#L264

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.

dlfivefifty avatar Aug 12 '20 09:08 dlfivefifty

Projective Infinity (the infinity of the Riemann sphere)

JeffreySarnoff avatar Aug 12 '20 10:08 JeffreySarnoff

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.

cjdoris avatar Aug 12 '20 10:08 cjdoris

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.

cjdoris avatar Aug 12 '20 10:08 cjdoris

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}}

dlfivefifty avatar Aug 12 '20 10:08 dlfivefifty

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?

dlfivefifty avatar Aug 12 '20 10:08 dlfivefifty

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

cjdoris avatar Aug 12 '20 10:08 cjdoris

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

dlfivefifty avatar Aug 12 '20 10:08 dlfivefifty

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

JeffreySarnoff avatar Aug 12 '20 10:08 JeffreySarnoff

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.

dlfivefifty avatar Aug 12 '20 11:08 dlfivefifty

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.

cjdoris avatar Aug 12 '20 11:08 cjdoris

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.

dlfivefifty avatar Aug 12 '20 11:08 dlfivefifty

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

dlfivefifty avatar Aug 12 '20 11:08 dlfivefifty

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

dlfivefifty avatar Aug 12 '20 11:08 dlfivefifty

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

cjdoris avatar Aug 12 '20 11:08 cjdoris

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.

cjdoris avatar Aug 12 '20 11:08 cjdoris

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.

cjdoris avatar Aug 12 '20 11:08 cjdoris

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.

dlfivefifty avatar Aug 12 '20 13:08 dlfivefifty

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.

dlfivefifty avatar Aug 12 '20 13:08 dlfivefifty

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.

cjdoris avatar Aug 12 '20 15:08 cjdoris

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

cjdoris avatar Aug 12 '20 15:08 cjdoris

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)

dlfivefifty avatar Aug 12 '20 16:08 dlfivefifty